diff --git a/.gitignore b/.gitignore
index 66fd13c903cac02eb9657cd53fb227823484401d..398baf21b2dc579eaffb17134409de9b729b0050 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@
# Dependency directories (remove the comment below to include it)
# vendor/
+
+.idea
diff --git a/README.en.md b/README.en.md
deleted file mode 100644
index 3ce8792497719c7dff9c2bb9d2e80d5ddf52fc81..0000000000000000000000000000000000000000
--- a/README.en.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# simpleactor-go
-
-#### Description
-protoactor-go简化版
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Instructions
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Contribution
-
-1. Fork the repository
-2. Create Feat_xxx branch
-3. Commit your code
-4. Create Pull Request
-
-
-#### Gitee Feature
-
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README.md b/README.md
index 762ca804192349e57bfcaf4414829927a02ca3c6..afb0cd6fa098080fb64920cd831c9d7f624439b9 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,4 @@
# simpleactor-go
-#### 介绍
-protoactor-go简化版
-
-#### 软件架构
-软件架构说明
-
-
-#### 安装教程
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 使用说明
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 参与贡献
-
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
-
-
-#### 特技
-
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+## 介绍
+protoactor-go简化版
\ No newline at end of file
diff --git a/_examples/Makefile b/_examples/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d3526fb9666386577cac41d2b9a736be1ab93323
--- /dev/null
+++ b/_examples/Makefile
@@ -0,0 +1,11 @@
+examples := $(wildcard $(CURDIR)/*/)
+
+
+.PHONY: go.mod
+go.mod:
+ @echo "go mod tidy"
+ @for d in $(examples); do \
+ echo $$d ; \
+ cd $$d && go mod tidy ; \
+ done
+
diff --git a/_examples/actor-autorespond/go.mod b/_examples/actor-autorespond/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..678f1e754826141fa258a955cd6814a86d60e353
--- /dev/null
+++ b/_examples/actor-autorespond/go.mod
@@ -0,0 +1,36 @@
+module autorespond
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-autorespond/go.sum b/_examples/actor-autorespond/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..616a6b1575c165294ea88ea440da8596a33cce3f
--- /dev/null
+++ b/_examples/actor-autorespond/go.sum
@@ -0,0 +1,98 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-autorespond/main.go b/_examples/actor-autorespond/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..b07604c6e21d551f46330fbb3989a66fd72fa1ea
--- /dev/null
+++ b/_examples/actor-autorespond/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Auto Response in Proto.Actor is a special kind of message that can create its own response message
+// it is received just like any other message by the actor
+// but the actor context sees the AutoResponse interface and calls GetAutoReplyMessage() to get the response message
+// this is useful if you want to guarantee some form of Ack from an actor. without forcing the developer of the actor to
+// use context.Respond manually
+
+// e.g. ClusterPubSub feature uses this to Ack back to the Topic actor to let it know the message has been received
+
+type myAutoResponder struct {
+ name string
+}
+
+func (m myAutoResponder) GetAutoResponse(context actor.Context) interface{} {
+ // return some response-message
+ // you have full access to the actor context
+
+ return &myAutoResponse{
+ name: m.name + " " + context.Self().Id,
+ }
+}
+
+type myAutoResponse struct {
+ name string
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ props := actor.PropsFromFunc(func(ctx actor.Context) {})
+ pid := system.Root.Spawn(props)
+
+ res, _ := system.Root.RequestFuture(pid, &myAutoResponder{name: "hello"}, 10*time.Second).Result()
+
+ fmt.Printf("%v", res)
+}
diff --git a/_examples/actor-backpressure/go.mod b/_examples/actor-backpressure/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..376161c1f8fde0991ef60e3133abf37db2b15eb8
--- /dev/null
+++ b/_examples/actor-backpressure/go.mod
@@ -0,0 +1,39 @@
+module backpressure
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-backpressure/go.sum b/_examples/actor-backpressure/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-backpressure/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-backpressure/main.go b/_examples/actor-backpressure/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4988e3810067e9c6c85ed016eaa2a71e5e9d58c7
--- /dev/null
+++ b/_examples/actor-backpressure/main.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ "log"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+// sent to producer to request more work
+type requestMoreWork struct {
+ items int
+}
+type requestWorkBehavior struct {
+ tokens int64
+ producer *actor.PID
+}
+
+func (m *requestWorkBehavior) MailboxStarted() {
+ m.requestMore()
+}
+
+func (m *requestWorkBehavior) MessagePosted(msg interface{}) {
+}
+
+func (m *requestWorkBehavior) MessageReceived(msg interface{}) {
+ atomic.AddInt64(&m.tokens, -1)
+ if m.tokens == 0 {
+ m.requestMore()
+ }
+}
+
+func (m *requestWorkBehavior) MailboxEmpty() {
+}
+
+func (m *requestWorkBehavior) requestMore() {
+ log.Println("Requesting more tokens")
+ m.tokens = 50
+ system.Root.Send(m.producer, &requestMoreWork{items: 50})
+}
+
+type producer struct {
+ requestedWork int
+ producedWork int
+ worker *actor.PID
+}
+
+func (p *producer) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ // spawn our worker
+ mb := actor.Unbounded(&requestWorkBehavior{
+ producer: ctx.Self(),
+ })
+ workerProps := actor.PropsFromProducer(func() actor.Actor {
+ return &worker{}
+ }, actor.WithMailbox(mb))
+
+ p.worker = ctx.Spawn(workerProps)
+ case *requestMoreWork:
+ p.requestedWork += msg.items
+ log.Println("Producer got a new work request")
+ ctx.Send(ctx.Self(), &produce{})
+ case *produce:
+ // produce more work
+ log.Println("Producer is producing work")
+ p.producedWork++
+ ctx.Send(p.worker, &work{p.producedWork})
+
+ // decrease our workload and tell ourselves to produce more work
+ if p.requestedWork > 0 {
+ p.requestedWork--
+ ctx.Send(ctx.Self(), &produce{})
+ }
+ }
+}
+
+type (
+ produce struct{}
+ worker struct{}
+)
+
+func (w *worker) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *work:
+ log.Printf("Worker is working %+v", msg)
+ time.Sleep(100 * time.Millisecond)
+ }
+}
+
+type work struct {
+ id int
+}
+
+var system = actor.NewActorSystem()
+
+func main() {
+ producerProps := actor.PropsFromProducer(func() actor.Actor { return &producer{} })
+ system.Root.Spawn(producerProps)
+
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-deadletter/Makefile b/_examples/actor-deadletter/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fd04ca92aa13629d9c4febbd307fe913c3edc5f7
--- /dev/null
+++ b/_examples/actor-deadletter/Makefile
@@ -0,0 +1,2 @@
+start:
+ go run main.go -duration 20s -rate 2000000 -throttle 3
diff --git a/_examples/actor-deadletter/go.mod b/_examples/actor-deadletter/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c4da551ce88c445c3b27b26c72deed80c6bbfaae
--- /dev/null
+++ b/_examples/actor-deadletter/go.mod
@@ -0,0 +1,40 @@
+module helloworld
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ golang.org/x/time v0.1.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-deadletter/go.sum b/_examples/actor-deadletter/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..5be0f8bcb6b906a1d3816693457eb37f5fd8a179
--- /dev/null
+++ b/_examples/actor-deadletter/go.sum
@@ -0,0 +1,102 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
+golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-deadletter/main.go b/_examples/actor-deadletter/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..433254702ee86f1a7e76a371125a87839f381eaa
--- /dev/null
+++ b/_examples/actor-deadletter/main.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "log"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+ "golang.org/x/time/rate"
+)
+
+type hello struct {
+ Who string
+}
+
+func main() {
+ irate := flag.Int("rate", 1000000, "How many messages per second")
+ throttle := flag.Int("throttle", 5, "Throttle of deadletter logs")
+ d := flag.Duration("duration", 10*time.Second, "How long you want to keep sending")
+ flag.Parse()
+
+ // init
+ cfg := actor.Configure(actor.WithDeadLetterThrottleCount(int32(*throttle)))
+ system := actor.NewActorSystemWithConfig(cfg)
+
+ btn := int32(1)
+ go func() {
+ time.Sleep(*d)
+ atomic.StoreInt32(&btn, 0)
+ }()
+
+ ctx := context.TODO()
+ invalidPid := system.NewLocalPID("unknown")
+ limiter := rate.NewLimiter(rate.Limit(*irate), *irate)
+
+ log.Printf("started")
+ for atomic.LoadInt32(&btn) == 1 {
+ system.Root.Send(invalidPid, &hello{Who: "deadleater"})
+ // time.Sleep(sleepDrt)
+ limiter.Wait(ctx)
+ }
+ log.Printf("done")
+ console.ReadLine()
+}
diff --git a/_examples/actor-helloworld/go.mod b/_examples/actor-helloworld/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c95a881d5464fb1b93c0e7ffa40a3d8a69d5d9bc
--- /dev/null
+++ b/_examples/actor-helloworld/go.mod
@@ -0,0 +1,39 @@
+module helloworld
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-helloworld/go.sum b/_examples/actor-helloworld/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-helloworld/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-helloworld/main.go b/_examples/actor-helloworld/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c0dd692cf8e824f2e534730b6c23024d1a630e5
--- /dev/null
+++ b/_examples/actor-helloworld/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type (
+ hello struct{ Who string }
+ helloActor struct{}
+)
+
+func (state *helloActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ props := actor.PropsFromProducer(func() actor.Actor { return &helloActor{} })
+
+ pid := system.Root.Spawn(props)
+ system.Root.Send(pid, &hello{Who: "Roger"})
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-inprocess-benchmark/go.mod b/_examples/actor-inprocess-benchmark/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..3ab68abe767cc859750ee3ffa88278ef49514bda
--- /dev/null
+++ b/_examples/actor-inprocess-benchmark/go.mod
@@ -0,0 +1,36 @@
+module inprocessbenchmark
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-inprocess-benchmark/go.sum b/_examples/actor-inprocess-benchmark/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..616a6b1575c165294ea88ea440da8596a33cce3f
--- /dev/null
+++ b/_examples/actor-inprocess-benchmark/go.sum
@@ -0,0 +1,98 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-inprocess-benchmark/inprocessbenchmark b/_examples/actor-inprocess-benchmark/inprocessbenchmark
new file mode 100644
index 0000000000000000000000000000000000000000..6df0bfa1f2265dad3b03d74c3a553185abaa8f44
Binary files /dev/null and b/_examples/actor-inprocess-benchmark/inprocessbenchmark differ
diff --git a/_examples/actor-inprocess-benchmark/main.go b/_examples/actor-inprocess-benchmark/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4513fb7fd9daa8ea693a8610c7ceda3836d4b075
--- /dev/null
+++ b/_examples/actor-inprocess-benchmark/main.go
@@ -0,0 +1,177 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type Msg struct {
+ Sender *actor.PID
+}
+type Start struct {
+ Sender *actor.PID
+}
+
+type pingActor struct {
+ count int
+ wgStop *sync.WaitGroup
+ messageCount int
+ batch int
+ batchSize int
+}
+
+func pongActor(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *Msg:
+ context.Send(msg.Sender, &Msg{Sender: context.Self()})
+ }
+}
+
+func (state *pingActor) sendBatch(context actor.Context, sender *actor.PID) bool {
+ if state.messageCount == 0 {
+ return false
+ }
+
+ var m interface{} = &Msg{
+ Sender: context.Self(),
+ }
+
+ for i := 0; i < state.batchSize; i++ {
+ context.Send(sender, m)
+ }
+
+ state.messageCount -= state.batchSize
+ state.batch = state.batchSize
+ return true
+}
+
+func (state *pingActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *Start:
+ state.sendBatch(context, msg.Sender)
+
+ case *Msg:
+ state.batch--
+ if state.batch > 0 {
+ return
+ }
+
+ if !state.sendBatch(context, msg.Sender) {
+ state.wgStop.Done()
+ }
+ }
+}
+
+func newPingActor(stop *sync.WaitGroup, messageCount int, batchSize int) actor.Producer {
+ return func() actor.Actor {
+ return &pingActor{
+ wgStop: stop,
+ messageCount: messageCount,
+ batchSize: batchSize,
+ }
+ }
+}
+
+var (
+ cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+ blockProfile = flag.String("blockprof", "", "execute contention profiling and save results here")
+)
+
+func main() {
+ flag.Parse()
+ if *cpuprofile != "" {
+ f, err := os.Create(*cpuprofile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+ }
+
+ // Check for lock contention profiling
+ if *blockProfile != "" {
+ prof, err := os.Create(*blockProfile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ runtime.SetBlockProfileRate(1)
+ defer func() {
+ pprof.Lookup("block").WriteTo(prof, 0)
+ }()
+ }
+
+ // runtime.GOMAXPROCS(runtime.NumCPU())
+ // runtime.GC()
+
+ system := actor.NewActorSystem()
+
+ var wg sync.WaitGroup
+
+ messageCount := 1000000
+ batchSize := 100
+ tps := []int{300, 400, 500, 600, 700, 800, 900}
+ log.Println("Dispatcher Throughput Elapsed Time Messages per sec")
+ for _, tp := range tps {
+
+ d := actor.NewDefaultDispatcher(tp)
+
+ clientProps := actor.
+ PropsFromProducer(newPingActor(&wg, messageCount, batchSize),
+ actor.WithMailbox(actor.Bounded(batchSize+10)),
+ actor.WithDispatcher(d))
+ rootContext := system.Root
+
+ echoProps := actor.
+ PropsFromFunc(pongActor,
+ actor.WithMailbox(actor.Bounded(batchSize+10)),
+ actor.WithDispatcher(d))
+
+ clients := make([]*actor.PID, 0)
+ echos := make([]*actor.PID, 0)
+ clientCount := runtime.NumCPU() * 2
+ for i := 0; i < clientCount; i++ {
+ client := rootContext.Spawn(clientProps)
+ echo := rootContext.Spawn(echoProps)
+ clients = append(clients, client)
+ echos = append(echos, echo)
+ wg.Add(1)
+ }
+ start := time.Now()
+
+ for i := 0; i < clientCount; i++ {
+ client := clients[i]
+ echo := echos[i]
+
+ rootContext.Send(client, &Start{
+ Sender: echo,
+ })
+ }
+
+ wg.Wait()
+ elapsed := time.Since(start)
+ x := int(float32(messageCount*2*clientCount) / (float32(elapsed) / float32(time.Second)))
+ log.Printf(" %v %s %v", tp, elapsed, x)
+ for i := 0; i < clientCount; i++ {
+ client := clients[i]
+ rootContext.StopFuture(client).Wait()
+ echo := echos[i]
+ rootContext.StopFuture(echo).Wait()
+ }
+ runtime.GC()
+ time.Sleep(2 * time.Second)
+ }
+
+ // f, err := os.Create("memprofile")
+ // if err != nil {
+ // log.Fatal(err)
+ // }
+ // pprof.WriteHeapProfile(f)
+ // f.Close()
+}
diff --git a/_examples/actor-jaegertracing/README.md b/_examples/actor-jaegertracing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..606d229460260f31806ba0597d4ddd5cd3597c27
--- /dev/null
+++ b/_examples/actor-jaegertracing/README.md
@@ -0,0 +1,17 @@
+# Jeager Tracing / OpenTracing example
+
+To run the example an instance of Jaeger server is required running locally. The easiest way to run a jaeger server
+instance is starting it using the included docker-compose file like this
+
+```bash
+docker-compose -f ./examples/jaegertracing/docker-compose.yaml up -d
+```
+
+And the just run the example:
+
+```bash
+go run ./examples/jaegertracing/main.go
+```
+
+After the test has run (and also during), traces can found using the Jaeger UI started at http://localhost:16686.
+
diff --git a/_examples/actor-jaegertracing/docker-compose.yaml b/_examples/actor-jaegertracing/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..15129cf00e2d70abbe917cea6c42547de0201dea
--- /dev/null
+++ b/_examples/actor-jaegertracing/docker-compose.yaml
@@ -0,0 +1,14 @@
+version: '3'
+services:
+ jaeger:
+ image: jaegertracing/all-in-one:1.7
+ environment:
+ COLLECTOR_ZIPKIN_HTTP_PORT: 9411
+ ports:
+ - '5775:5775/udp'
+ - '6831:6831/udp'
+ - '6832:6832/udp'
+ - '5778:5778'
+ - '16686:16686'
+ - '14268:14268'
+ - '9411:9411'
\ No newline at end of file
diff --git a/_examples/actor-jaegertracing/go.mod b/_examples/actor-jaegertracing/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c7eb4d6dbba88ab8a8337ed3edcab8d8b3e6bb9d
--- /dev/null
+++ b/_examples/actor-jaegertracing/go.mod
@@ -0,0 +1,46 @@
+module jaegertracing
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ github.com/uber/jaeger-client-go v2.25.0+incompatible
+ github.com/uber/jaeger-lib v2.4.0+incompatible
+)
+
+require (
+ github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/opentracing/opentracing-go v1.2.0 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-jaegertracing/go.sum b/_examples/actor-jaegertracing/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..24501d8bdfdc10dfa527bc83e3ad8caf924c0119
--- /dev/null
+++ b/_examples/actor-jaegertracing/go.sum
@@ -0,0 +1,149 @@
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
+github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
+github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=
+github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/_examples/actor-jaegertracing/main.go b/_examples/actor-jaegertracing/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..5967ffae38535cd670e1455d89c28fca7e868968
--- /dev/null
+++ b/_examples/actor-jaegertracing/main.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "math/rand"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/actor/middleware/opentracing"
+ console "github.com/asynkron/goconsole"
+ jaegercfg "github.com/uber/jaeger-client-go/config"
+ jaegerlog "github.com/uber/jaeger-client-go/log"
+ "github.com/uber/jaeger-lib/metrics"
+)
+
+func main() {
+ jaegerCloser := initJaeger()
+ defer jaegerCloser.Close()
+
+ system := actor.NewActorSystem()
+ rootContext := actor.
+ NewRootContext(system, nil).
+ WithSpawnMiddleware(opentracing.TracingMiddleware())
+
+ pid := rootContext.SpawnPrefix(createProps(5), "root")
+ for i := 0; i < 3; i++ {
+ rootContext.RequestFuture(pid, &request{i}, 10*time.Second).Wait()
+ }
+ _, _ = console.ReadLine()
+}
+
+func initJaeger() io.Closer {
+ // Sample configuration for testing. Use constant sampling to sample every trace
+ // and enable LogSpan to log every span via configured Logger.
+ cfg := jaegercfg.Configuration{
+ Sampler: &jaegercfg.SamplerConfig{
+ Type: jaeger.SamplerTypeConst,
+ Param: 1,
+ },
+ Reporter: &jaegercfg.ReporterConfig{
+ LogSpans: true,
+ },
+ }
+
+ // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
+ // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
+ // frameworks.
+ jLogger := jaegerlog.StdLogger
+ jMetricsFactory := metrics.NullFactory
+
+ // Initialize tracer with a logger and a metrics factory
+ closer, err := cfg.InitGlobalTracer(
+ "jaeger-test",
+ jaegercfg.Logger(jLogger),
+ jaegercfg.Metrics(jMetricsFactory),
+ )
+ if err != nil {
+ // log.Printf("Could not initialize jaeger tracer: %s", err.Error())
+ panic(fmt.Sprintf("Could not initialize jaeger tracer: %s", err.Error()))
+ }
+ return closer
+}
+
+func createProps(levels int) *actor.Props {
+ if levels <= 1 {
+ sleep := time.Duration(rand.Intn(5000))
+
+ return actor.PropsFromFunc(func(c actor.Context) {
+ switch msg := c.Message().(type) {
+ case *request:
+ time.Sleep(sleep * time.Millisecond)
+ if c.Sender() != nil {
+ c.Respond(&response{i: msg.i})
+ }
+ }
+ })
+ }
+
+ var childs []*actor.PID
+ return actor.PropsFromFunc(func(c actor.Context) {
+ switch c.Message().(type) {
+ case *actor.Started:
+ for i := 0; i < 3; i++ {
+ childs = append(childs, c.Spawn(createProps(levels-1)))
+ }
+ case *request:
+ c.Forward(childs[rand.Intn(len(childs))])
+ }
+ })
+}
+
+type request struct {
+ i int
+}
+
+type response struct {
+ i int
+}
diff --git a/_examples/actor-jaegertracing/router/main.go b/_examples/actor-jaegertracing/router/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..6efca4e03650435f00813f52a8f7fc0a95205085
--- /dev/null
+++ b/_examples/actor-jaegertracing/router/main.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "math/rand"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/actor/middleware/opentracing"
+ "gitee.com/simplexyz/simpleactor-go/router"
+ jaegercfg "github.com/uber/jaeger-client-go/config"
+ jaegerlog "github.com/uber/jaeger-client-go/log"
+ "github.com/uber/jaeger-lib/metrics"
+)
+
+func main() {
+ actorSystem := actor.NewActorSystem()
+ jaegerCloser := initJaeger()
+ defer jaegerCloser.Close()
+
+ rootContext := actor.
+ NewRootContext(actorSystem, nil).
+ WithSpawnMiddleware(opentracing.TracingMiddleware())
+
+ pid := rootContext.SpawnPrefix(createProps(router.NewRoundRobinPool, 3), "root")
+ for i := 0; i < 3; i++ {
+ _ = rootContext.RequestFuture(pid, &request{i}, 10*time.Second).Wait()
+ }
+ _, _ = console.ReadLine()
+}
+
+func initJaeger() io.Closer {
+ // Sample configuration for testing. Use constant sampling to sample every trace
+ // and enable LogSpan to log every span via configured Logger.
+ cfg := jaegercfg.Configuration{
+ Sampler: &jaegercfg.SamplerConfig{
+ Type: jaeger.SamplerTypeConst,
+ Param: 1,
+ },
+ Reporter: &jaegercfg.ReporterConfig{
+ LogSpans: true,
+ },
+ }
+
+ // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
+ // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
+ // frameworks.
+ jLogger := jaegerlog.StdLogger
+ jMetricsFactory := metrics.NullFactory
+
+ // Initialize tracer with a logger and a metrics factory
+ closer, err := cfg.InitGlobalTracer(
+ "jaeger-test",
+ jaegercfg.Logger(jLogger),
+ jaegercfg.Metrics(jMetricsFactory),
+ )
+ if err != nil {
+ // log.Printf("Could not initialize jaeger tracer: %s", err.Error())
+ panic(fmt.Sprintf("Could not initialize jaeger tracer: %s", err.Error()))
+ }
+ return closer
+}
+
+func createProps(routerFunc func(size int) *actor.Props, levels int) *actor.Props {
+ if levels == 1 {
+ sleep := time.Duration(rand.Intn(5000))
+ return routerFunc(3).WithFunc(func(c actor.Context) {
+ switch msg := c.Message().(type) {
+ case *request:
+ time.Sleep(sleep * time.Millisecond)
+ if c.Sender() != nil {
+ c.Respond(&response{i: msg.i})
+ }
+ }
+ })
+ }
+ var childPID *actor.PID
+ return routerFunc(5).WithFunc(func(c actor.Context) {
+ switch c.Message().(type) {
+ case *actor.Started:
+ childPID = c.Spawn(createProps(routerFunc, levels-1))
+ case *request:
+ c.Forward(childPID)
+ }
+ })
+}
+
+type request struct {
+ i int
+}
+
+type response struct {
+ i int
+}
diff --git a/_examples/actor-lifecycleevents/go.mod b/_examples/actor-lifecycleevents/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..fa3f8e37d9a9f3b477f331d651205b5d4e34de63
--- /dev/null
+++ b/_examples/actor-lifecycleevents/go.mod
@@ -0,0 +1,39 @@
+module lifecycleevents
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-lifecycleevents/go.sum b/_examples/actor-lifecycleevents/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-lifecycleevents/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-lifecycleevents/main.go b/_examples/actor-lifecycleevents/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..3630fc34b010d8c51ea8a2b3e9e0f93eeb54ad0a
--- /dev/null
+++ b/_examples/actor-lifecycleevents/main.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type (
+ hello struct{ Who string }
+ helloActor struct{}
+)
+
+func (state *helloActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *actor.Started:
+ fmt.Println("Started, initialize actor here")
+ case *actor.Stopping:
+ fmt.Println("Stopping, actor is about shut down")
+ case *actor.Stopped:
+ fmt.Println("Stopped, actor and its children are stopped")
+ case *actor.Restarting:
+ fmt.Println("Restarting, actor is about restart")
+ case *hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ panic("test")
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ props := actor.PropsFromProducer(func() actor.Actor { return &helloActor{} })
+ pid := system.Root.Spawn(props)
+ system.Root.Send(pid, &hello{Who: "Roger"})
+
+ // why wait?
+ // Stop is a system message and is not processed through the user message mailbox
+ // thus, it will be handled _before_ any user message
+ // we only do this to show the correct order of events in the console
+ time.Sleep(1 * time.Second)
+ system.Root.Stop(pid)
+
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-mailbox-middleware/go.mod b/_examples/actor-mailbox-middleware/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..1b4ccbe5f402bd6e7ba69b673eaa4784cc098962
--- /dev/null
+++ b/_examples/actor-mailbox-middleware/go.mod
@@ -0,0 +1,39 @@
+module mailboxstats
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-mailbox-middleware/go.sum b/_examples/actor-mailbox-middleware/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-mailbox-middleware/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-mailbox-middleware/main.go b/_examples/actor-mailbox-middleware/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d7163e4d14d5018fe7b97293ad368b4873145fb4
--- /dev/null
+++ b/_examples/actor-mailbox-middleware/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "log"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type mailboxLogger struct{}
+
+func (m *mailboxLogger) MailboxStarted() {
+ log.Print("Mailbox started")
+}
+
+func (m *mailboxLogger) MessagePosted(msg interface{}) {
+ log.Printf("Message posted %v", msg)
+}
+
+func (m *mailboxLogger) MessageReceived(msg interface{}) {
+ log.Printf("Message received %v", msg)
+}
+
+func (m *mailboxLogger) MailboxEmpty() {
+ log.Print("No more messages")
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ }, actor.WithMailbox(actor.Unbounded(&mailboxLogger{})))
+ pid := rootContext.Spawn(props)
+ rootContext.Send(pid, "Hello")
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-messagebatch/go.mod b/_examples/actor-messagebatch/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..b21ebed16e55c716d88b2e5ffa64a25f755704d2
--- /dev/null
+++ b/_examples/actor-messagebatch/go.mod
@@ -0,0 +1,39 @@
+module messagebatch
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-messagebatch/go.sum b/_examples/actor-messagebatch/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-messagebatch/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-messagebatch/main.go b/_examples/actor-messagebatch/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..edd162e6ab61c0c9a76bfeb78f7312b955e4d439
--- /dev/null
+++ b/_examples/actor-messagebatch/main.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "fmt"
+ "strconv"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+// MessageBatch is a message that is sent to the actor and unpacks its payload in the mailbox
+// This allows you to group messages together and send them as a single message
+// while processing them as individual messages
+// this is used by the Cluster PubSub feature to send a batch of messages and then Ack to the entire batch
+// In that specific case, both MessageBatch and AutoRespond are required
+
+type myMessageBatch struct {
+ messages []interface{}
+}
+
+func (m myMessageBatch) GetMessages() []interface{} {
+ return m.messages
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ if m, ok := ctx.Message().(string); ok {
+ fmt.Println(m)
+ }
+ })
+ pid := system.Root.Spawn(props)
+
+ messages := make([]interface{}, 0)
+
+ for i := 0; i < 100; i++ {
+ messages = append(messages, "Hello"+strconv.Itoa(i))
+ }
+
+ batch := &myMessageBatch{
+ messages: messages,
+ }
+ system.Root.Send(pid, batch)
+
+ console.ReadLine()
+}
diff --git a/_examples/actor-mixins/go.mod b/_examples/actor-mixins/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..5a7e1ca4b9f87c2b109f05ffb647d96762103974
--- /dev/null
+++ b/_examples/actor-mixins/go.mod
@@ -0,0 +1,39 @@
+module mixins
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-mixins/go.sum b/_examples/actor-mixins/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-mixins/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-mixins/main.go b/_examples/actor-mixins/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..9994bb91ca7a4bbeff62b21df4745c5421173fc3
--- /dev/null
+++ b/_examples/actor-mixins/main.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/actor/middleware"
+ "gitee.com/simplexyz/simpleactor-go/plugin"
+ console "github.com/asynkron/goconsole"
+)
+
+type myActor struct {
+ NameAwareHolder
+}
+
+func (state *myActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ case *actor.Started:
+ // this actor have been initialized by the receive pipeline
+ fmt.Printf("My name is %v\n", state.name)
+ }
+}
+
+type NameAware interface {
+ SetName(name string)
+}
+
+type NameAwareHolder struct {
+ name string
+}
+
+func (state *NameAwareHolder) SetName(name string) {
+ state.name = name
+}
+
+type NamerPlugin struct{}
+
+func (p *NamerPlugin) OnStart(ctx actor.ReceiverContext) {
+ if p, ok := ctx.Actor().(NameAware); ok {
+ p.SetName("Proto.Actor")
+ }
+}
+func (p *NamerPlugin) OnOtherMessage(ctx actor.ReceiverContext, env *actor.MessageEnvelope) {}
+
+func main() {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ props := actor.
+ PropsFromProducer(func() actor.Actor { return &myActor{} },
+ actor.WithReceiverMiddleware(
+ plugin.Use(&NamerPlugin{}),
+ middleware.Logger,
+ ))
+
+ pid := rootContext.Spawn(props)
+ rootContext.Send(pid, "bar")
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-receive-middleware/go.mod b/_examples/actor-receive-middleware/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..5213e04aa83117cf600c983a1784c5b41500c52d
--- /dev/null
+++ b/_examples/actor-receive-middleware/go.mod
@@ -0,0 +1,39 @@
+module receivepipeline
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-receive-middleware/go.sum b/_examples/actor-receive-middleware/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-receive-middleware/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-receive-middleware/main.go b/_examples/actor-receive-middleware/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..56a5cdada71f6a3cc8d6efe6c291c1762816d137
--- /dev/null
+++ b/_examples/actor-receive-middleware/main.go
@@ -0,0 +1,27 @@
+package main
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/actor/middleware"
+ console "github.com/asynkron/goconsole"
+)
+
+type hello struct{ Who string }
+
+func receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ props := actor.PropsFromFunc(receive, actor.WithReceiverMiddleware(middleware.Logger))
+ pid := rootContext.Spawn(props)
+ rootContext.Send(pid, &hello{Who: "Roger"})
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-receive-timeout/go.mod b/_examples/actor-receive-timeout/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..624f92d6baa81e24dadd5939477766823bcaa9ab
--- /dev/null
+++ b/_examples/actor-receive-timeout/go.mod
@@ -0,0 +1,39 @@
+module receivetimeout
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-receive-timeout/go.sum b/_examples/actor-receive-timeout/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-receive-timeout/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-receive-timeout/main.go b/_examples/actor-receive-timeout/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..188ca45473edb6d39e859565d9b66ee471d7cd40
--- /dev/null
+++ b/_examples/actor-receive-timeout/main.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type NoInfluence string
+
+func (NoInfluence) NotInfluenceReceiveTimeout() {}
+
+func main() {
+ log.Println("Receive timeout test")
+
+ system := actor.NewActorSystem()
+ c := 0
+
+ rootContext := system.Root
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *actor.Started:
+ context.SetReceiveTimeout(1 * time.Second)
+
+ case *actor.ReceiveTimeout:
+ c++
+ log.Printf("ReceiveTimeout: %d", c)
+
+ case string:
+ log.Printf("received '%s'", msg)
+ if msg == "cancel" {
+ fmt.Println("Cancelling")
+ context.CancelReceiveTimeout()
+ }
+
+ case NoInfluence:
+ log.Println("received a no-influence message")
+
+ }
+ })
+
+ pid := rootContext.Spawn(props)
+ for i := 0; i < 6; i++ {
+ rootContext.Send(pid, "hello")
+ time.Sleep(500 * time.Millisecond)
+ }
+
+ log.Println("hit [return] to send no-influence messages")
+ _, _ = console.ReadLine()
+
+ for i := 0; i < 6; i++ {
+ rootContext.Send(pid, NoInfluence("hello"))
+ time.Sleep(500 * time.Millisecond)
+ }
+
+ log.Println("hit [return] to send a message to cancel the timeout")
+ _, _ = console.ReadLine()
+ rootContext.Send(pid, "cancel")
+
+ log.Println("hit [return] to finish")
+ _, _ = console.ReadLine()
+
+ rootContext.Stop(pid)
+}
diff --git a/_examples/actor-request-response/go.mod b/_examples/actor-request-response/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..37e95d65dcc814dd8757756a92c59d3c3547b51a
--- /dev/null
+++ b/_examples/actor-request-response/go.mod
@@ -0,0 +1,39 @@
+module requestresponse
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-request-response/go.sum b/_examples/actor-request-response/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-request-response/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-request-response/main.go b/_examples/actor-request-response/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d1925728dfe297a93aa055baaabac8e501038154
--- /dev/null
+++ b/_examples/actor-request-response/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type Hello struct{ Who string }
+
+func Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case Hello:
+ context.Respond("Hello " + msg.Who)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ props := actor.PropsFromFunc(Receive)
+ pid := rootContext.Spawn(props)
+ result, _ := rootContext.RequestFuture(pid, Hello{Who: "Roger"}, 30*time.Second).Result() // await result
+
+ fmt.Println(result)
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-setbehavior/README.MD b/_examples/actor-setbehavior/README.MD
new file mode 100644
index 0000000000000000000000000000000000000000..c1124f73a3b210f7a93b0bacf45b315b9f0605e6
--- /dev/null
+++ b/_examples/actor-setbehavior/README.MD
@@ -0,0 +1,68 @@
+# Switchable Behaviors
+
+Actors have the power to switch their behaviors at any point in time. This is usually referred as *becoming something*,
+as in *the actor becomes busy* or *the actor becomes connected*.
+
+This is accomplished by replacing the method that handles messages inside the actor using `SetBehavior`
+or `PushBehavior`.
+These methods accept a delegate that will handle the next messages until you decide to replace it again.
+
+This is a powerful concept that is behind other features like Finite State Machines.
+
+> **Note:**
When you change the actor behavior, the new behaviour will take effect for all subsequent messages
+> until the behaviour is changed again. The current message will continue processing with the existing behaviour.
+> You can use Stashing to reprocess the current message with the new behavior.
+
+## API
+
+The API to change behaviors is available to the actor instance is very simple:
+
+* `Become` - Replaces the message handler with the specified delegate;
+* `BecomeStacked` - Adds the specified message handler to the top of the behavior stack, while maintaining the previous
+ ones;
+* `UnbecomeStacked` - Reverts to the previous message handler from the stack (only works with PushBehavior);
+
+```go
+...
+
+type Hello struct{ Who string }
+type SetBehaviorActor struct {
+ behavior actor.Behavior
+}
+
+func (state *SetBehaviorActor) Receive(context actor.Context) {
+ state.behavior.Receive(context)
+}
+
+func (state *SetBehaviorActor) One(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case Hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ state.behavior.Become(state.Other)
+ }
+}
+
+func (state *SetBehaviorActor) Other(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case Hello:
+ fmt.Printf("%v, ey we are now handling messages in another behavior", msg.Who)
+ }
+}
+
+func NewSetBehaviorActor() actor.Actor {
+ act := &SetBehaviorActor{
+ behavior: actor.NewBehavior(),
+ }
+ act.behavior.Become(act.One)
+ return act
+}
+
+func main() {
+ rootContext := actor.EmptyRootContext
+ props := actor.PropsFromProducer(NewSetBehaviorActor)
+ pid, _ := rootContext.Spawn(props)
+ rootContext.Send(pid, Hello{Who: "Roger"})
+ rootContext.Send(pid, Hello{Who: "Roger"})
+ console.ReadLine()
+}
+```
\ No newline at end of file
diff --git a/_examples/actor-setbehavior/go.mod b/_examples/actor-setbehavior/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..e6516063935085696db10ed5cc19832da42c505c
--- /dev/null
+++ b/_examples/actor-setbehavior/go.mod
@@ -0,0 +1,39 @@
+module setbehavior
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-setbehavior/go.sum b/_examples/actor-setbehavior/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-setbehavior/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-setbehavior/main.go b/_examples/actor-setbehavior/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..009cd065cef4700b0b440bf178c8451ce2f8ba34
--- /dev/null
+++ b/_examples/actor-setbehavior/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "fmt"
+
+ console "github.com/asynkron/goconsole"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type (
+ Hello struct{ Who string }
+ SetBehaviorActor struct {
+ behavior actor.Behavior
+ }
+)
+
+func (state *SetBehaviorActor) Receive(context actor.Context) {
+ state.behavior.Receive(context)
+}
+
+func (state *SetBehaviorActor) One(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case Hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ state.behavior.Become(state.Other)
+ }
+}
+
+func (state *SetBehaviorActor) Other(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case Hello:
+ fmt.Printf("%v, ey we are now handling messages in another behavior", msg.Who)
+ }
+}
+
+func NewSetBehaviorActor() actor.Actor {
+ act := &SetBehaviorActor{
+ behavior: actor.NewBehavior(),
+ }
+ act.behavior.Become(act.One)
+ return act
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ props := actor.PropsFromProducer(NewSetBehaviorActor)
+ pid := rootContext.Spawn(props)
+ rootContext.Send(pid, Hello{Who: "Roger"})
+ rootContext.Send(pid, Hello{Who: "Roger"})
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/actor-spawn-benchmark/go.mod b/_examples/actor-spawn-benchmark/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c415defeb33a062204b21323f9b4b0286a372128
--- /dev/null
+++ b/_examples/actor-spawn-benchmark/go.mod
@@ -0,0 +1,36 @@
+module spawnbenchmark
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-spawn-benchmark/go.sum b/_examples/actor-spawn-benchmark/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..616a6b1575c165294ea88ea440da8596a33cce3f
--- /dev/null
+++ b/_examples/actor-spawn-benchmark/go.sum
@@ -0,0 +1,98 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-spawn-benchmark/main.go b/_examples/actor-spawn-benchmark/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4514d157e5fe4a092f6d234b1fe4069bca82a74
--- /dev/null
+++ b/_examples/actor-spawn-benchmark/main.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "runtime/pprof"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type request struct {
+ num int
+ size int
+ div int
+}
+
+var props = actor.PropsFromProducer(newState, actor.WithMailbox(actor.Unbounded()))
+
+type state struct {
+ sum int
+ replies int
+ replyTo *actor.PID
+}
+
+func newState() actor.Actor {
+ return &state{}
+}
+
+func (s *state) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *request:
+ if msg.size == 1 {
+ ctx.Respond(msg.num)
+ return
+ }
+
+ s.replies = msg.div
+ s.replyTo = ctx.Sender()
+ for i := 0; i < msg.div; i++ {
+ child := ctx.ActorSystem().Root.Spawn(props)
+ ctx.Request(child, &request{
+ num: msg.num + i*(msg.size/msg.div),
+ size: msg.size / msg.div,
+ div: msg.div,
+ })
+ }
+ case int:
+ s.sum += msg
+ s.replies--
+ if s.replies == 0 {
+ ctx.Send(s.replyTo, s.sum)
+ }
+ }
+}
+
+var (
+ cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+ memprofile = flag.String("memprofile", "", "write mem profile to file")
+)
+
+func main() {
+ flag.Parse()
+ if *cpuprofile != "" {
+ f, err := os.Create(*cpuprofile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ _ = pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+ }
+ // runtime.GOMAXPROCS(runtime.NumCPU())
+ // runtime.GC()
+
+ for i := 0; i < 10; i++ {
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+
+ start := time.Now()
+ pid := rootContext.Spawn(props)
+ res, _ := rootContext.RequestFuture(pid, &request{
+ num: 0,
+ size: 1000000,
+ div: 10,
+ }, 10*time.Second).Result()
+ result := res.(int)
+
+ took := time.Since(start)
+ fmt.Printf("Result: %d in %d ms.\n", result, took.Nanoseconds()/1e6)
+ }
+
+ if *memprofile != "" {
+ f, err := os.Create(*memprofile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ _ = pprof.WriteHeapProfile(f)
+ _ = f.Close()
+ return
+ }
+}
diff --git a/_examples/actor-supervision/go.mod b/_examples/actor-supervision/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..8518fe209683f12649ddc9bacb3876438a6114ff
--- /dev/null
+++ b/_examples/actor-supervision/go.mod
@@ -0,0 +1,39 @@
+module supervision
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/actor-supervision/go.sum b/_examples/actor-supervision/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/actor-supervision/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/actor-supervision/main.go b/_examples/actor-supervision/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..56a11572d5b9cb87a791f3b5fd0ad86babbb1ffb
--- /dev/null
+++ b/_examples/actor-supervision/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type (
+ hello struct{ Who string }
+ parentActor struct{}
+)
+
+func (state *parentActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *hello:
+ props := actor.PropsFromProducer(newChildActor)
+ child := context.Spawn(props)
+ context.Send(child, msg)
+ }
+}
+
+func newParentActor() actor.Actor {
+ return &parentActor{}
+}
+
+type childActor struct{}
+
+func (state *childActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *actor.Started:
+ fmt.Println("Starting, initialize actor here")
+ case *actor.Stopping:
+ fmt.Println("Stopping, actor is about to shut down")
+ case *actor.Stopped:
+ fmt.Println("Stopped, actor and its children are stopped")
+ case *actor.Restarting:
+ fmt.Println("Restarting, actor is about to restart")
+ case *hello:
+ fmt.Printf("Hello %v\n", msg.Who)
+ panic("Ouch")
+ }
+}
+
+func newChildActor() actor.Actor {
+ return &childActor{}
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ decider := func(reason interface{}) actor.Directive {
+ fmt.Println("handling failure for child")
+ return actor.StopDirective
+ }
+ supervisor := actor.NewOneForOneStrategy(10, 1000, decider)
+ rootContext := system.Root
+ props := actor.
+ PropsFromProducer(newParentActor,
+ actor.WithSupervisor(supervisor))
+
+ pid := rootContext.Spawn(props)
+ rootContext.Send(pid, &hello{Who: "Roger"})
+
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/cluster-basic/Makefile b/_examples/cluster-basic/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0bb231412a50360c6f984c3a358922c50ce05e49
--- /dev/null
+++ b/_examples/cluster-basic/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node1/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node2/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/cluster-basic/docker-compose.yml b/_examples/cluster-basic/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3cfdc0477bbf722994271019dd9d88438116a101
--- /dev/null
+++ b/_examples/cluster-basic/docker-compose.yml
@@ -0,0 +1,48 @@
+version: '3.7'
+services:
+
+ consul-agent-1: &consul-agent
+ image: hashicorp/consul:latest
+ networks:
+ - consul
+ command: "agent -retry-join consul-server-bootstrap -client 0.0.0.0"
+
+ consul-agent-2:
+ <<: *consul-agent
+
+ consul-agent-3:
+ <<: *consul-agent
+
+ consul-server-1: &consul-server
+ <<: *consul-agent
+ command: "agent -server -retry-join consul-server-bootstrap -client 0.0.0.0"
+
+ consul-server-2:
+ <<: *consul-server
+
+ consul-server-bootstrap:
+ <<: *consul-agent
+ ports:
+ - "8400:8400"
+ - "8500:8500"
+ - "8600:8600"
+ - "8600:8600/udp"
+ command: "agent -server -bootstrap-expect 3 -ui -client 0.0.0.0"
+
+ mongodb:
+ image: mongo:latest
+ ports:
+ - 127.0.0.1:27017:27017
+ volumes:
+ - mongodb_data:/data/db
+
+ redis:
+ image: redis:latest
+ ports:
+ - 127.0.0.1:6379:6379
+
+networks:
+ consul:
+
+volumes:
+ mongodb_data:
\ No newline at end of file
diff --git a/_examples/cluster-basic/go.mod b/_examples/cluster-basic/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..f113ae9900b7c9ddd5e43613102d518d68574f0e
--- /dev/null
+++ b/_examples/cluster-basic/go.mod
@@ -0,0 +1,60 @@
+module cluster-broadcast
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/btree v1.0.1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/consul/api v1.18.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-basic/go.sum b/_examples/cluster-basic/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..198f0c3fd3de7cb23fa4607c1a34c58ec417b305
--- /dev/null
+++ b/_examples/cluster-basic/go.sum
@@ -0,0 +1,322 @@
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-basic/node1/main.go b/_examples/cluster-basic/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8b80b56349fd67e5dddb695d555501d1cf4eef7
--- /dev/null
+++ b/_examples/cluster-basic/node1/main.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ c := startNode()
+
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+ pid := c.Get("abc", "hello")
+ fmt.Printf("Got pid %v", pid)
+ fmt.Println()
+ console.ReadLine()
+ c.Shutdown(true)
+}
+
+func startNode() *cluster.Cluster {
+ system := actor.NewActorSystem()
+
+ provider, _ := consul.New()
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+ c := cluster.New(system, clusterConfig)
+ c.StartMember()
+
+ return c
+}
diff --git a/_examples/cluster-basic/node2/main.go b/_examples/cluster-basic/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..f774027c6a546b40a56e42733db209b89531bc3d
--- /dev/null
+++ b/_examples/cluster-basic/node2/main.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "fmt"
+
+ "cluster-broadcast/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ cluster := startNode()
+
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+
+ cluster.Shutdown(true)
+}
+
+func startNode() *cluster.Cluster {
+ system := actor.NewActorSystem()
+
+ provider, _ := consul.New()
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ fmt.Printf("Started %v", msg)
+ case *shared.Hello:
+ fmt.Printf("Hello %v\n", msg.Name)
+ }
+ })
+ helloKind := cluster.NewKind("hello", props)
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config, cluster.WithKinds(helloKind))
+ c := cluster.New(system, clusterConfig)
+
+ c.StartMember()
+ return c
+}
diff --git a/_examples/cluster-basic/shared/build.sh b/_examples/cluster-basic/shared/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..28f9be836fdee9993e2446e07361301c1eac81a5
--- /dev/null
+++ b/_examples/cluster-basic/shared/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. -I=$GOPATH/src --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-basic/shared/protos.pb.go b/_examples/cluster-basic/shared/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..82cc0c5ef0e6ca16ff0c6e2c5674d7ea01193ed6
--- /dev/null
+++ b/_examples/cluster-basic/shared/protos.pb.go
@@ -0,0 +1,144 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package shared
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Hello struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *Hello) Reset() {
+ *x = Hello{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Hello) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Hello) ProtoMessage() {}
+
+func (x *Hello) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Hello.ProtoReflect.Descriptor instead.
+func (*Hello) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Hello) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x1b, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12,
+ 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+ 0x61, 0x6d, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+ 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x61, 0x73, 0x69, 0x63,
+ 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_protos_proto_goTypes = []interface{}{
+ (*Hello)(nil), // 0: shared.Hello
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Hello); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-basic/shared/protos.proto b/_examples/cluster-basic/shared/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..b71bd07a72a107d74ad12787498771718b65a1de
--- /dev/null
+++ b/_examples/cluster-basic/shared/protos.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+package shared;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-basic/shared";
+
+message Hello {
+ string name = 1;
+}
diff --git a/_examples/cluster-broadcast/Makefile b/_examples/cluster-broadcast/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0bb231412a50360c6f984c3a358922c50ce05e49
--- /dev/null
+++ b/_examples/cluster-broadcast/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node1/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node2/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/cluster-broadcast/go.mod b/_examples/cluster-broadcast/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a3db93305c033928b9ccf76198b4f2edfc45265f
--- /dev/null
+++ b/_examples/cluster-broadcast/go.mod
@@ -0,0 +1,54 @@
+module cluster-broadcast
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/labstack/echo v3.3.10+incompatible // indirect
+ github.com/labstack/gommon v0.3.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v1.2.1 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sync v0.3.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-broadcast/go.sum b/_examples/cluster-broadcast/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..f5151bdf77fa2b926bd72affe6b715e74a4f7b0f
--- /dev/null
+++ b/_examples/cluster-broadcast/go.sum
@@ -0,0 +1,150 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
+github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
+github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
+github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-broadcast/node1/main.go b/_examples/cluster-broadcast/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b89826cb17a27c5329405251af3b8336303813c
--- /dev/null
+++ b/_examples/cluster-broadcast/node1/main.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "cluster-broadcast/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/automanaged"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ shared.TrackerFactory(func() shared.Tracker {
+ return &shared.TrackGrain{}
+ })
+ shared.CalculatorFactory(func() shared.Calculator {
+ return &shared.CalcGrain{}
+ })
+
+ c := startNode(8080)
+
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+
+ fmt.Print("\nAdding 1 Egg - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Eggs", 1)
+
+ fmt.Print("\nAdding 10 Egg - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Eggs", 10)
+
+ fmt.Print("\nAdding 100 Bananas - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Bananas", 100)
+
+ fmt.Print("\nAdding 2 Meat - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Meat", 3)
+ calcAdd(c, "Meat", 9000)
+
+ getAll(c)
+
+ console.ReadLine()
+
+ c.Shutdown(true)
+}
+
+func startNode(port int64) *cluster.Cluster {
+ // how long before the grain poisons itself
+ system := actor.NewActorSystem()
+
+ provider := automanaged.NewWithConfig(2*time.Second, 6331, "localhost:6330", "localhost:6331")
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+
+ calculatorKind := shared.NewCalculatorKind(func() shared.Calculator {
+ return &shared.CalcGrain{}
+ }, 0)
+
+ trackerKind := shared.NewTrackerKind(func() shared.Tracker {
+ return &shared.TrackGrain{}
+ }, 0)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config,
+ cluster.WithKinds(calculatorKind, trackerKind))
+
+ cluster := cluster.New(system, clusterConfig)
+
+ shared.TrackerFactory(func() shared.Tracker {
+ return &shared.TrackGrain{}
+ })
+
+ cluster.StartMember()
+
+ return cluster
+}
+
+func calcAdd(cluster *cluster.Cluster, grainId string, addNumber int64) {
+ calcGrain := shared.GetCalculatorGrainClient(cluster, grainId)
+ total1, err := calcGrain.Add(&shared.NumberRequest{Number: addNumber})
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Grain: %v - Total: %v \n", calcGrain.Identity, total1.Number)
+}
+
+func getAll(cluster *cluster.Cluster) {
+ trackerGrain := shared.GetTrackerGrainClient(cluster, "singleTrackerGrain")
+ totals, err := trackerGrain.BroadcastGetCounts(&shared.Noop{})
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("--- Totals ---")
+ for grainId, count := range totals.Totals {
+ fmt.Printf("%v : %v\n", grainId, count)
+ }
+}
diff --git a/_examples/cluster-broadcast/node2/main.go b/_examples/cluster-broadcast/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..2196f532b09c88ab02e397725e666709553cd0a7
--- /dev/null
+++ b/_examples/cluster-broadcast/node2/main.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "cluster-broadcast/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/automanaged"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ c := startNode(8081)
+
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+
+ fmt.Print("\nAdding 1 Egg - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Eggs", 1)
+
+ fmt.Print("\nAdding 10 Egg - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Eggs", 10)
+
+ fmt.Print("\nAdding 100 Bananas - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Bananas", 100)
+
+ fmt.Print("\nAdding 2 Meat - Enter\n")
+ console.ReadLine()
+ calcAdd(c, "Meat", 3)
+ calcAdd(c, "Meat", 9000)
+
+ getAll(c)
+
+ console.ReadLine()
+
+ c.Shutdown(true)
+}
+
+func startNode(port int64) *cluster.Cluster {
+ system := actor.NewActorSystem()
+
+ provider := automanaged.NewWithConfig(2*time.Second, 6330, "localhost:6330", "localhost:6331")
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+
+ calculatorKind := shared.NewCalculatorKind(func() shared.Calculator {
+ return &shared.CalcGrain{}
+ }, 0)
+
+ trackerKind := shared.NewTrackerKind(func() shared.Tracker {
+ return &shared.TrackGrain{}
+ }, 0)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config,
+ cluster.WithKinds(calculatorKind, trackerKind))
+
+ cluster := cluster.New(system, clusterConfig)
+
+ cluster.StartMember()
+ return cluster
+}
+
+func calcAdd(cluster *cluster.Cluster, grainId string, addNumber int64) {
+ calcGrain := shared.GetCalculatorGrainClient(cluster, grainId)
+ total1, err := calcGrain.Add(&shared.NumberRequest{Number: addNumber})
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("Grain: %v - Total: %v \n", calcGrain.Identity, total1.Number)
+}
+
+func getAll(cluster *cluster.Cluster) {
+ trackerGrain := shared.GetTrackerGrainClient(cluster, "singleTrackerGrain")
+ totals, err := trackerGrain.BroadcastGetCounts(&shared.Noop{})
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("--- Totals ---")
+ for grainId, count := range totals.Totals {
+ fmt.Printf("%v : %v\n", grainId, count)
+ }
+}
diff --git a/_examples/cluster-broadcast/shared/build.sh b/_examples/cluster-broadcast/shared/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..a0b752d4944adc1d2e473eb03c2e6bf766dad636
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. --gograinv2_out=. protos.proto
\ No newline at end of file
diff --git a/_examples/cluster-broadcast/shared/calculator-grain.go b/_examples/cluster-broadcast/shared/calculator-grain.go
new file mode 100644
index 0000000000000000000000000000000000000000..0e2860e5bca44fef4211836fd51cb63397941992
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/calculator-grain.go
@@ -0,0 +1,40 @@
+package shared
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+type CalcGrain struct {
+ total int64
+}
+
+func (c *CalcGrain) ReceiveDefault(ctx cluster.GrainContext) {
+}
+
+func (c *CalcGrain) Init(ctx cluster.GrainContext) {
+ c.total = 0
+
+ // register with the tracker
+ trackerGrain := GetTrackerGrainClient(ctx.Cluster(), "singleTrackerGrain")
+ trackerGrain.RegisterGrain(&RegisterMessage{GrainId: ctx.Identity()})
+}
+
+func (c *CalcGrain) Terminate(ctx cluster.GrainContext) {
+ // deregister with the tracker
+ trackerGrain := GetTrackerGrainClient(ctx.Cluster(), "singleTrackerGrain")
+ trackerGrain.DeregisterGrain(&RegisterMessage{GrainId: ctx.Identity()})
+}
+
+func (c *CalcGrain) Add(n *NumberRequest, ctx cluster.GrainContext) (*CountResponse, error) {
+ c.total = c.total + n.Number
+ return &CountResponse{Number: c.total}, nil
+}
+
+func (c *CalcGrain) Subtract(n *NumberRequest, ctx cluster.GrainContext) (*CountResponse, error) {
+ c.total = c.total - n.Number
+ return &CountResponse{Number: c.total}, nil
+}
+
+func (c *CalcGrain) GetCurrent(n *Noop, ctx cluster.GrainContext) (*CountResponse, error) {
+ return &CountResponse{Number: c.total}, nil
+}
diff --git a/_examples/cluster-broadcast/shared/protos.pb.go b/_examples/cluster-broadcast/shared/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..b4df29b391222878a08f5f56fab1dbd64eeefb38
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/protos.pb.go
@@ -0,0 +1,428 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package shared
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Noop struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Noop) Reset() {
+ *x = Noop{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Noop) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Noop) ProtoMessage() {}
+
+func (x *Noop) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Noop.ProtoReflect.Descriptor instead.
+func (*Noop) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type NumberRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *NumberRequest) Reset() {
+ *x = NumberRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NumberRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NumberRequest) ProtoMessage() {}
+
+func (x *NumberRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NumberRequest.ProtoReflect.Descriptor instead.
+func (*NumberRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *NumberRequest) GetNumber() int64 {
+ if x != nil {
+ return x.Number
+ }
+ return 0
+}
+
+type CountResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *CountResponse) Reset() {
+ *x = CountResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CountResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CountResponse) ProtoMessage() {}
+
+func (x *CountResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CountResponse.ProtoReflect.Descriptor instead.
+func (*CountResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CountResponse) GetNumber() int64 {
+ if x != nil {
+ return x.Number
+ }
+ return 0
+}
+
+type RegisterMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ GrainId string `protobuf:"bytes,1,opt,name=grain_id,json=grainId,proto3" json:"grain_id,omitempty"`
+}
+
+func (x *RegisterMessage) Reset() {
+ *x = RegisterMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RegisterMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RegisterMessage) ProtoMessage() {}
+
+func (x *RegisterMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RegisterMessage.ProtoReflect.Descriptor instead.
+func (*RegisterMessage) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *RegisterMessage) GetGrainId() string {
+ if x != nil {
+ return x.GrainId
+ }
+ return ""
+}
+
+type TotalsResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Totals map[string]int64 `protobuf:"bytes,1,rep,name=totals,proto3" json:"totals,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+}
+
+func (x *TotalsResponse) Reset() {
+ *x = TotalsResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *TotalsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TotalsResponse) ProtoMessage() {}
+
+func (x *TotalsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use TotalsResponse.ProtoReflect.Descriptor instead.
+func (*TotalsResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *TotalsResponse) GetTotals() map[string]int64 {
+ if x != nil {
+ return x.Totals
+ }
+ return nil
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x06, 0x0a, 0x04, 0x4e, 0x6f, 0x6f, 0x70, 0x22, 0x27,
+ 0x0a, 0x0d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x27, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62,
+ 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+ 0x22, 0x2c, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x87,
+ 0x01, 0x0a, 0x0e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x12, 0x3a, 0x0a, 0x06, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c,
+ 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73,
+ 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x1a, 0x39, 0x0a,
+ 0x0b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+ 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+ 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76,
+ 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xb4, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x6c,
+ 0x63, 0x75, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x35, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x12, 0x15,
+ 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x43,
+ 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a,
+ 0x0a, 0x08, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x15, 0x2e, 0x73, 0x68, 0x61,
+ 0x72, 0x65, 0x64, 0x2e, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0a, 0x47, 0x65,
+ 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65,
+ 0x64, 0x2e, 0x4e, 0x6f, 0x6f, 0x70, 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e,
+ 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32,
+ 0xbd, 0x01, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x0d, 0x52,
+ 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x73,
+ 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x4e,
+ 0x6f, 0x6f, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0f, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73,
+ 0x74, 0x65, 0x72, 0x47, 0x72, 0x61, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65,
+ 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+ 0x65, 0x1a, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x4e, 0x6f, 0x6f, 0x70, 0x22,
+ 0x00, 0x12, 0x3c, 0x0a, 0x12, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x47, 0x65,
+ 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64,
+ 0x2e, 0x4e, 0x6f, 0x6f, 0x70, 0x1a, 0x16, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x54,
+ 0x6f, 0x74, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
+ 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73,
+ 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f,
+ 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74,
+ 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_protos_proto_goTypes = []interface{}{
+ (*Noop)(nil), // 0: shared.Noop
+ (*NumberRequest)(nil), // 1: shared.NumberRequest
+ (*CountResponse)(nil), // 2: shared.CountResponse
+ (*RegisterMessage)(nil), // 3: shared.RegisterMessage
+ (*TotalsResponse)(nil), // 4: shared.TotalsResponse
+ nil, // 5: shared.TotalsResponse.TotalsEntry
+}
+var file_protos_proto_depIdxs = []int32{
+ 5, // 0: shared.TotalsResponse.totals:type_name -> shared.TotalsResponse.TotalsEntry
+ 1, // 1: shared.Calculator.Add:input_type -> shared.NumberRequest
+ 1, // 2: shared.Calculator.Subtract:input_type -> shared.NumberRequest
+ 0, // 3: shared.Calculator.GetCurrent:input_type -> shared.Noop
+ 3, // 4: shared.Tracker.RegisterGrain:input_type -> shared.RegisterMessage
+ 3, // 5: shared.Tracker.DeregisterGrain:input_type -> shared.RegisterMessage
+ 0, // 6: shared.Tracker.BroadcastGetCounts:input_type -> shared.Noop
+ 2, // 7: shared.Calculator.Add:output_type -> shared.CountResponse
+ 2, // 8: shared.Calculator.Subtract:output_type -> shared.CountResponse
+ 2, // 9: shared.Calculator.GetCurrent:output_type -> shared.CountResponse
+ 0, // 10: shared.Tracker.RegisterGrain:output_type -> shared.Noop
+ 0, // 11: shared.Tracker.DeregisterGrain:output_type -> shared.Noop
+ 4, // 12: shared.Tracker.BroadcastGetCounts:output_type -> shared.TotalsResponse
+ 7, // [7:13] is the sub-list for method output_type
+ 1, // [1:7] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Noop); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NumberRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CountResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RegisterMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*TotalsResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 6,
+ NumExtensions: 0,
+ NumServices: 2,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-broadcast/shared/protos.proto b/_examples/cluster-broadcast/shared/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..5099070ebb3387116d52fb4615caa332c2efa8a6
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/protos.proto
@@ -0,0 +1,33 @@
+syntax = "proto3";
+package shared;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-broadcast/shared";
+
+message Noop {}
+
+message NumberRequest {
+ int64 number = 1;
+}
+
+message CountResponse {
+ int64 number = 1;
+}
+
+service Calculator {
+ rpc Add(NumberRequest) returns (CountResponse) {}
+ rpc Subtract(NumberRequest) returns (CountResponse) {}
+ rpc GetCurrent(Noop) returns (CountResponse) {}
+}
+
+message RegisterMessage {
+ string grain_id = 1;
+}
+
+message TotalsResponse {
+ map totals = 1;
+}
+
+service Tracker {
+ rpc RegisterGrain(RegisterMessage) returns (Noop) {}
+ rpc DeregisterGrain(RegisterMessage) returns (Noop) {}
+ rpc BroadcastGetCounts(Noop) returns (TotalsResponse) {}
+}
diff --git a/_examples/cluster-broadcast/shared/protos_protoactor.go b/_examples/cluster-broadcast/shared/protos_protoactor.go
new file mode 100644
index 0000000000000000000000000000000000000000..109cb792c80656f5c68e33b5b7f8fec03cc882ee
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/protos_protoactor.go
@@ -0,0 +1,510 @@
+// Package shared is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package shared
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][shared]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+var xCalculatorFactory func() Calculator
+
+// CalculatorFactory produces a Calculator
+func CalculatorFactory(factory func() Calculator) {
+ xCalculatorFactory = factory
+}
+
+// GetCalculatorGrainClient instantiates a new CalculatorGrainClient with given Identity
+func GetCalculatorGrainClient(c *cluster.Cluster, id string) *CalculatorGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &CalculatorGrainClient{Identity: id, cluster: c}
+}
+
+// GetCalculatorKind instantiates a new cluster.Kind for Calculator
+func GetCalculatorKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &CalculatorActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Calculator", props)
+ return kind
+}
+
+// GetCalculatorKind instantiates a new cluster.Kind for Calculator
+func NewCalculatorKind(factory func() Calculator, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xCalculatorFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &CalculatorActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Calculator", props)
+ return kind
+}
+
+// Calculator interfaces the services available to the Calculator
+type Calculator interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ Add(*NumberRequest, cluster.GrainContext) (*CountResponse, error)
+ Subtract(*NumberRequest, cluster.GrainContext) (*CountResponse, error)
+ GetCurrent(*Noop, cluster.GrainContext) (*CountResponse, error)
+}
+
+// CalculatorGrainClient holds the base data for the CalculatorGrain
+type CalculatorGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// Add requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) Add(r *NumberRequest, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// Subtract requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) Subtract(r *NumberRequest, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// GetCurrent requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) GetCurrent(r *Noop, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// CalculatorActor represents the actor structure
+type CalculatorActor struct {
+ ctx cluster.GrainContext
+ inner Calculator
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *CalculatorActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xCalculatorFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &NumberRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Add(NumberRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Add(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Add(NumberRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 1:
+ req := &NumberRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Subtract(NumberRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Subtract(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Subtract(NumberRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 2:
+ req := &Noop{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("GetCurrent(Noop) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.GetCurrent(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("GetCurrent(Noop) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
+
+var xTrackerFactory func() Tracker
+
+// TrackerFactory produces a Tracker
+func TrackerFactory(factory func() Tracker) {
+ xTrackerFactory = factory
+}
+
+// GetTrackerGrainClient instantiates a new TrackerGrainClient with given Identity
+func GetTrackerGrainClient(c *cluster.Cluster, id string) *TrackerGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &TrackerGrainClient{Identity: id, cluster: c}
+}
+
+// GetTrackerKind instantiates a new cluster.Kind for Tracker
+func GetTrackerKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &TrackerActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Tracker", props)
+ return kind
+}
+
+// GetTrackerKind instantiates a new cluster.Kind for Tracker
+func NewTrackerKind(factory func() Tracker, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xTrackerFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &TrackerActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Tracker", props)
+ return kind
+}
+
+// Tracker interfaces the services available to the Tracker
+type Tracker interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ RegisterGrain(*RegisterMessage, cluster.GrainContext) (*Noop, error)
+ DeregisterGrain(*RegisterMessage, cluster.GrainContext) (*Noop, error)
+ BroadcastGetCounts(*Noop, cluster.GrainContext) (*TotalsResponse, error)
+}
+
+// TrackerGrainClient holds the base data for the TrackerGrain
+type TrackerGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// RegisterGrain requests the execution on to the cluster with CallOptions
+func (g *TrackerGrainClient) RegisterGrain(r *RegisterMessage, opts ...cluster.GrainCallOption) (*Noop, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Tracker", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &Noop{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// DeregisterGrain requests the execution on to the cluster with CallOptions
+func (g *TrackerGrainClient) DeregisterGrain(r *RegisterMessage, opts ...cluster.GrainCallOption) (*Noop, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Tracker", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &Noop{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// BroadcastGetCounts requests the execution on to the cluster with CallOptions
+func (g *TrackerGrainClient) BroadcastGetCounts(r *Noop, opts ...cluster.GrainCallOption) (*TotalsResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Tracker", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &TotalsResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// TrackerActor represents the actor structure
+type TrackerActor struct {
+ ctx cluster.GrainContext
+ inner Tracker
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *TrackerActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xTrackerFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &RegisterMessage{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("RegisterGrain(RegisterMessage) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.RegisterGrain(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("RegisterGrain(RegisterMessage) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 1:
+ req := &RegisterMessage{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("DeregisterGrain(RegisterMessage) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.DeregisterGrain(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("DeregisterGrain(RegisterMessage) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 2:
+ req := &Noop{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("BroadcastGetCounts(Noop) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.BroadcastGetCounts(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("BroadcastGetCounts(Noop) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
diff --git a/_examples/cluster-broadcast/shared/tracker-grain.go b/_examples/cluster-broadcast/shared/tracker-grain.go
new file mode 100644
index 0000000000000000000000000000000000000000..866042eea6baee1527687b75af00636b09d6e349
--- /dev/null
+++ b/_examples/cluster-broadcast/shared/tracker-grain.go
@@ -0,0 +1,49 @@
+package shared
+
+import (
+ "fmt"
+ "strings"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+type TrackGrain struct {
+ grainsMap map[string]bool
+}
+
+func (t *TrackGrain) ReceiveDefault(ctx cluster.GrainContext) {
+}
+
+func (t *TrackGrain) Init(ctx cluster.GrainContext) {
+ t.grainsMap = map[string]bool{}
+}
+
+func (t *TrackGrain) Terminate(ctx cluster.GrainContext) {
+}
+
+func (t *TrackGrain) RegisterGrain(n *RegisterMessage, ctx cluster.GrainContext) (*Noop, error) {
+ parts := strings.Split(n.GrainId, "/")
+ grainID := parts[len(parts)-1]
+ t.grainsMap[grainID] = true
+ return &Noop{}, nil
+}
+
+func (t *TrackGrain) DeregisterGrain(n *RegisterMessage, ctx cluster.GrainContext) (*Noop, error) {
+ delete(t.grainsMap, n.GrainId)
+ return &Noop{}, nil
+}
+
+func (t *TrackGrain) BroadcastGetCounts(n *Noop, ctx cluster.GrainContext) (*TotalsResponse, error) {
+ totals := map[string]int64{}
+ for grainAddress := range t.grainsMap {
+ calcGrain := GetCalculatorGrainClient(ctx.Cluster(), grainAddress)
+ grainTotal, err := calcGrain.GetCurrent(&Noop{})
+ if err != nil {
+ fmt.Sprintf("Grain %s issued an error : %s", grainAddress, err)
+ }
+ fmt.Sprintf("Grain %s - %v", grainAddress, grainTotal.Number)
+ totals[grainAddress] = grainTotal.Number
+ }
+
+ return &TotalsResponse{Totals: totals}, nil
+}
diff --git a/_examples/cluster-eventstream-broadcast/build.sh b/_examples/cluster-eventstream-broadcast/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..28f9be836fdee9993e2446e07361301c1eac81a5
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. -I=$GOPATH/src --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-eventstream-broadcast/go.mod b/_examples/cluster-eventstream-broadcast/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c09da302c5ed8e0f4aa471b29a9b5b9925370aeb
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/go.mod
@@ -0,0 +1,54 @@
+module cluster-evenstream-broadcast
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ github.com/google/uuid v1.3.0
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/labstack/echo v3.3.10+incompatible // indirect
+ github.com/labstack/gommon v0.3.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v1.2.1 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sync v0.3.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-eventstream-broadcast/go.sum b/_examples/cluster-eventstream-broadcast/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..f5151bdf77fa2b926bd72affe6b715e74a4f7b0f
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/go.sum
@@ -0,0 +1,150 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
+github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
+github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
+github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-eventstream-broadcast/main.go b/_examples/cluster-eventstream-broadcast/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..abff006e0a8b45ab40a51fcff22b0a1965d3da99
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/main.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "strings"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/automanaged"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+ "github.com/google/uuid"
+)
+
+func main() {
+ remotingPort, clusteringPort, clusterMembers := getArgs()
+
+ cluster := startNode(remotingPort, clusteringPort, clusterMembers)
+
+ cancelPublisher := publish(cluster)
+ cancelSubscriber := subscribe(cluster)
+
+ console.ReadLine()
+
+ cancelPublisher()
+ cancelSubscriber()
+
+ cluster.Shutdown(true)
+}
+
+func startNode(remotingPort int, clusteringPort int, clusterMembers []string) *cluster.Cluster {
+ system := actor.NewActorSystem()
+
+ provider := automanaged.NewWithConfig(2*time.Second, clusteringPort, clusterMembers...)
+ lookup := disthash.New()
+ config := remote.Configure("localhost", remotingPort)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+ cluster := cluster.New(system, clusterConfig)
+
+ cluster.StartMember()
+
+ return cluster
+}
+
+func publish(cluster *cluster.Cluster) (cancel func()) {
+ id := uuid.New().String()[:8]
+ done := make(chan struct{})
+
+ ticker := time.NewTicker(time.Second)
+
+ go func() {
+ for {
+ select {
+ case <-done:
+ return
+ case <-ticker.C:
+ fmt.Printf("==>> Publisher %s sending message\n", id)
+ event := &MyEvent{
+ Description: fmt.Sprintf("Hello from %s at %s", id, time.Now().Format(time.RFC3339)),
+ }
+ cluster.MemberList.BroadcastEvent(event, true)
+ }
+ }
+ }()
+
+ return func() {
+ ticker.Stop()
+ done <- struct{}{}
+ }
+}
+
+func subscribe(cluster *cluster.Cluster) (cancel func()) {
+ subscription := cluster.ActorSystem.EventStream.Subscribe(func(evt interface{}) {
+ if event, ok := evt.(*MyEvent); ok {
+ fmt.Printf("<<== Subscriber received event: %s\n", event.Description)
+ }
+ })
+
+ return func() {
+ cluster.ActorSystem.EventStream.Unsubscribe(subscription)
+ }
+}
+
+func getArgs() (remotingPort int, clusteringPort int, clusterMembers []string) {
+ flag.IntVar(&remotingPort, "remoting-port", 18080, "port for actor remote communication")
+ flag.IntVar(&clusteringPort, "clustering-port", 28080, "port for cluster provider communication")
+
+ var membersString string
+ flag.StringVar(&membersString, "members", "localhost:28080", "cluster members e.g. `--members=localhost:28080,localhost:28081")
+
+ flag.Parse()
+
+ clusterMembers = strings.Split(membersString, ",")
+
+ return
+}
diff --git a/_examples/cluster-eventstream-broadcast/protos.pb.go b/_examples/cluster-eventstream-broadcast/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..e75b544e387ae3f98d714a9513829daa80516164
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/protos.pb.go
@@ -0,0 +1,146 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package main
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type MyEvent struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"`
+}
+
+func (x *MyEvent) Reset() {
+ *x = MyEvent{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MyEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MyEvent) ProtoMessage() {}
+
+func (x *MyEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MyEvent.ProtoReflect.Descriptor instead.
+func (*MyEvent) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MyEvent) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04,
+ 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x2b, 0x0a, 0x07, 0x4d, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
+ 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
+ 0x6e, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73,
+ 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x74,
+ 0x72, 0x65, 0x61, 0x6d, 0x2d, 0x62, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x2f, 0x6d,
+ 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_protos_proto_goTypes = []interface{}{
+ (*MyEvent)(nil), // 0: main.MyEvent
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MyEvent); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-eventstream-broadcast/protos.proto b/_examples/cluster-eventstream-broadcast/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..ebf7a53480814cbefe65b5d9d11f621ce7a0192d
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/protos.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-eventstream-broadcast/main";
+package main;
+
+message MyEvent {
+ string description = 1;
+}
\ No newline at end of file
diff --git a/_examples/cluster-eventstream-broadcast/run.sh b/_examples/cluster-eventstream-broadcast/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..40808c42834b385475129f97c778ea1bfa5a75c0
--- /dev/null
+++ b/_examples/cluster-eventstream-broadcast/run.sh
@@ -0,0 +1,6 @@
+tmux new-session -d -s eg
+tmux split-window -t "eg:0" -v
+tmux send-keys -t "eg:0.0" "go run ./... --remoting-port=18080 --clustering-port=28080 --members=localhost:28080,localhost:28081" Enter
+tmux send-keys -t "eg:0.1" "go run ./... --remoting-port=18081 --clustering-port=28081 --members=localhost:28080,localhost:28081" Enter
+tmux attach -t eg
+tmux kill-session -t eg
\ No newline at end of file
diff --git a/_examples/cluster-grain/Makefile b/_examples/cluster-grain/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0bb231412a50360c6f984c3a358922c50ce05e49
--- /dev/null
+++ b/_examples/cluster-grain/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node1/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node2/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/cluster-grain/docker-compose.yml b/_examples/cluster-grain/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3cfdc0477bbf722994271019dd9d88438116a101
--- /dev/null
+++ b/_examples/cluster-grain/docker-compose.yml
@@ -0,0 +1,48 @@
+version: '3.7'
+services:
+
+ consul-agent-1: &consul-agent
+ image: hashicorp/consul:latest
+ networks:
+ - consul
+ command: "agent -retry-join consul-server-bootstrap -client 0.0.0.0"
+
+ consul-agent-2:
+ <<: *consul-agent
+
+ consul-agent-3:
+ <<: *consul-agent
+
+ consul-server-1: &consul-server
+ <<: *consul-agent
+ command: "agent -server -retry-join consul-server-bootstrap -client 0.0.0.0"
+
+ consul-server-2:
+ <<: *consul-server
+
+ consul-server-bootstrap:
+ <<: *consul-agent
+ ports:
+ - "8400:8400"
+ - "8500:8500"
+ - "8600:8600"
+ - "8600:8600/udp"
+ command: "agent -server -bootstrap-expect 3 -ui -client 0.0.0.0"
+
+ mongodb:
+ image: mongo:latest
+ ports:
+ - 127.0.0.1:27017:27017
+ volumes:
+ - mongodb_data:/data/db
+
+ redis:
+ image: redis:latest
+ ports:
+ - 127.0.0.1:6379:6379
+
+networks:
+ consul:
+
+volumes:
+ mongodb_data:
\ No newline at end of file
diff --git a/_examples/cluster-grain/go.mod b/_examples/cluster-grain/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..8f747d5e2e3c94ba4f18a969979a6a78d5c866a9
--- /dev/null
+++ b/_examples/cluster-grain/go.mod
@@ -0,0 +1,60 @@
+module cluster-grain
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/btree v1.0.1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/consul/api v1.18.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-grain/go.sum b/_examples/cluster-grain/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..198f0c3fd3de7cb23fa4607c1a34c58ec417b305
--- /dev/null
+++ b/_examples/cluster-grain/go.sum
@@ -0,0 +1,322 @@
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-grain/node1/main.go b/_examples/cluster-grain/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..e52f470a7150d876872c232e2f8959f184d87260
--- /dev/null
+++ b/_examples/cluster-grain/node1/main.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "fmt"
+
+ "cluster-grain/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ system := actor.NewActorSystem()
+
+ provider, _ := consul.New()
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+ c := cluster.New(system, clusterConfig)
+ c.StartMember()
+
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+ client := shared.GetHelloGrainClient(c, "mygrain1")
+ res, err := client.SayHello(&shared.HelloRequest{
+ Name: "World",
+ })
+ if err != nil {
+ fmt.Printf("Error: %v\n", err)
+ return
+ }
+
+ fmt.Printf("Response: %v\n", res)
+ fmt.Println()
+ console.ReadLine()
+
+ res, err = client.SayHello(&shared.HelloRequest{
+ Name: "World",
+ })
+ if err != nil {
+ fmt.Printf("Error: %v\n", err)
+ return
+ }
+
+ fmt.Printf("Response: %v\n", res)
+ fmt.Println()
+
+ console.ReadLine()
+ c.Shutdown(true)
+}
diff --git a/_examples/cluster-grain/node2/main.go b/_examples/cluster-grain/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..588fa5616fba10c988d3f4442864ff7b47bf2689
--- /dev/null
+++ b/_examples/cluster-grain/node2/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "fmt"
+
+ "cluster-grain/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+type HelloGrain struct{}
+
+func (h HelloGrain) Init(ctx cluster.GrainContext) {}
+func (h HelloGrain) Terminate(ctx cluster.GrainContext) {}
+func (h HelloGrain) ReceiveDefault(ctx cluster.GrainContext) {}
+
+func (h HelloGrain) SayHello(request *shared.HelloRequest, ctx cluster.GrainContext) (*shared.HelloResponse, error) {
+ return &shared.HelloResponse{Message: "Hello " + request.Name}, nil
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ provider, _ := consul.New()
+ lookup := disthash.New()
+ remoteConfig := remote.Configure("localhost", 0)
+ helloKind := shared.NewHelloKind(func() shared.Hello {
+ return &HelloGrain{}
+ }, 0)
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, remoteConfig,
+ cluster.WithKinds(helloKind))
+
+ c := cluster.New(system, clusterConfig)
+ c.StartMember()
+ fmt.Print("\nBoot other nodes and press Enter\n")
+ console.ReadLine()
+ c.Shutdown(true)
+}
diff --git a/_examples/cluster-grain/shared/build.sh b/_examples/cluster-grain/shared/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..28f9be836fdee9993e2446e07361301c1eac81a5
--- /dev/null
+++ b/_examples/cluster-grain/shared/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. -I=$GOPATH/src --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-grain/shared/protos.pb.go b/_examples/cluster-grain/shared/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..9b006f45331fee34a75aa7a4e1c1500ca117f069
--- /dev/null
+++ b/_examples/cluster-grain/shared/protos.pb.go
@@ -0,0 +1,214 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package shared
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HelloRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *HelloRequest) Reset() {
+ *x = HelloRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *HelloRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type HelloResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (x *HelloResponse) Reset() {
+ *x = HelloResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResponse) ProtoMessage() {}
+
+func (x *HelloResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
+func (*HelloResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x29, 0x0a, 0x0d, 0x48, 0x65,
+ 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x42, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x39,
+ 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x14, 0x2e, 0x73, 0x68, 0x61,
+ 0x72, 0x65, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e,
+ 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
+ 0x2d, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_protos_proto_goTypes = []interface{}{
+ (*HelloRequest)(nil), // 0: shared.HelloRequest
+ (*HelloResponse)(nil), // 1: shared.HelloResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // 0: shared.Hello.SayHello:input_type -> shared.HelloRequest
+ 1, // 1: shared.Hello.SayHello:output_type -> shared.HelloResponse
+ 1, // [1:2] is the sub-list for method output_type
+ 0, // [0:1] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-grain/shared/protos.proto b/_examples/cluster-grain/shared/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..85e9c6594a4d858aef520f67dc1fc81a87ea9dd8
--- /dev/null
+++ b/_examples/cluster-grain/shared/protos.proto
@@ -0,0 +1,15 @@
+syntax = "proto3";
+package shared;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-grain/shared";
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloResponse {
+ string message = 1;
+}
+
+service Hello {
+ rpc SayHello (HelloRequest) returns (HelloResponse) {}
+}
\ No newline at end of file
diff --git a/_examples/cluster-grain/shared/protos_protoactor.go b/_examples/cluster-grain/shared/protos_protoactor.go
new file mode 100644
index 0000000000000000000000000000000000000000..e067db5115455e8fd490eb4918b6801248edcd49
--- /dev/null
+++ b/_examples/cluster-grain/shared/protos_protoactor.go
@@ -0,0 +1,165 @@
+// Package shared is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package shared
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][shared]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+var xHelloFactory func() Hello
+
+// HelloFactory produces a Hello
+func HelloFactory(factory func() Hello) {
+ xHelloFactory = factory
+}
+
+// GetHelloGrainClient instantiates a new HelloGrainClient with given Identity
+func GetHelloGrainClient(c *cluster.Cluster, id string) *HelloGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &HelloGrainClient{Identity: id, cluster: c}
+}
+
+// GetHelloKind instantiates a new cluster.Kind for Hello
+func GetHelloKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &HelloActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Hello", props)
+ return kind
+}
+
+// GetHelloKind instantiates a new cluster.Kind for Hello
+func NewHelloKind(factory func() Hello, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xHelloFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &HelloActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Hello", props)
+ return kind
+}
+
+// Hello interfaces the services available to the Hello
+type Hello interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ SayHello(*HelloRequest, cluster.GrainContext) (*HelloResponse, error)
+}
+
+// HelloGrainClient holds the base data for the HelloGrain
+type HelloGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// SayHello requests the execution on to the cluster with CallOptions
+func (g *HelloGrainClient) SayHello(r *HelloRequest, opts ...cluster.GrainCallOption) (*HelloResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Hello", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &HelloResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// HelloActor represents the actor structure
+type HelloActor struct {
+ ctx cluster.GrainContext
+ inner Hello
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *HelloActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xHelloFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &HelloRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("SayHello(HelloRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.SayHello(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("SayHello(HelloRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
diff --git a/_examples/cluster-metrics/Makefile b/_examples/cluster-metrics/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..31c0c5ec6ed33c896fd17d2ff77980b5475d8146
--- /dev/null
+++ b/_examples/cluster-metrics/Makefile
@@ -0,0 +1,19 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux split-window -t "eg:0.0" -h
+ tmux select-pane -t "eg:0.2"
+ tmux send-keys -t "eg:0.0" "go run server/main.go -port 8080" Enter
+ tmux send-keys -t "eg:0.1" "go run server/main.go -port 8081" Enter
+ tmux send-keys -t "eg:0.2" "go run client/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
+
+
+proto:
+ protoc -I=. --gogoslick_out=. shared/*.proto
+ protoc -I=. --gograinv2_out=. shared/*.proto
diff --git a/_examples/cluster-metrics/client/main.go b/_examples/cluster-metrics/client/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbe84a04f9666b41e401ea9d6a8a8084507c2869
--- /dev/null
+++ b/_examples/cluster-metrics/client/main.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "cluster-metrics/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ system := actor.NewActorSystem()
+ config := remote.Configure("localhost", 0)
+
+ provider, _ := consul.New()
+ lookup := disthash.New()
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+ c := cluster.New(system, clusterConfig)
+ setupLogger(c)
+ c.StartMember()
+
+ callopts := cluster.NewGrainCallOptions(c).WithTimeout(5 * time.Second).WithRetry(5)
+ doRequests(c, callopts)
+ doRequestsAsync(c, callopts)
+ console.ReadLine()
+}
+
+func doRequests(c *cluster.Cluster, callopts *cluster.GrainCallConfig) {
+ msg := &shared.HelloRequest{Name: "Proto.Actor"}
+ helloGrain := shared.GetHelloGrainClient(c, "MyGrain123")
+ // with default callopts
+ resp, err := helloGrain.SayHello(msg)
+ if err != nil {
+ log.Fatalf("SayHello failed. err:%v", err)
+ }
+
+ // with custom callopts
+ resp, err = helloGrain.SayHello(msg, callopts)
+ if err != nil {
+ log.Fatalf("SayHello failed. err:%v", err)
+ }
+ log.Printf("Message from SayHello: %v", resp.Message)
+ for i := 0; i < 10000; i++ {
+ grainId := fmt.Sprintf("hello%v", i)
+ x := shared.GetHelloGrainClient(c, grainId)
+ x.SayHello(&shared.HelloRequest{Name: grainId})
+ }
+ log.Println("Done")
+}
+
+func doRequestsAsync(c *cluster.Cluster, callopts *cluster.GrainCallConfig) {
+ // sorry, golang has not magic, just use goroutine.
+ go func() {
+ doRequests(c, callopts)
+ }()
+}
+
+func setupLogger(c *cluster.Cluster) {
+ system := c.ActorSystem
+ // Subscribe
+ system.EventStream.Subscribe(func(event interface{}) {
+ switch msg := event.(type) {
+ case *cluster.MemberJoinedEvent:
+ log.Printf("Member Joined " + msg.Name())
+ case *cluster.MemberLeftEvent:
+ log.Printf("Member Left " + msg.Name())
+ case *cluster.MemberRejoinedEvent:
+ log.Printf("Member Rejoined " + msg.Name())
+ case *cluster.MemberUnavailableEvent:
+ log.Printf("Member Unavailable " + msg.Name())
+ case *cluster.MemberAvailableEvent:
+ log.Printf("Member Available " + msg.Name())
+ case cluster.ClusterTopology:
+ log.Printf("Cluster Topology Poll")
+ }
+ })
+}
diff --git a/_examples/cluster-metrics/go.mod b/_examples/cluster-metrics/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..8506f93a7870de17b8b4331ecff29fd211aff5d6
--- /dev/null
+++ b/_examples/cluster-metrics/go.mod
@@ -0,0 +1,60 @@
+module cluster-metrics
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/btree v1.0.1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/consul/api v1.18.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-metrics/go.sum b/_examples/cluster-metrics/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..198f0c3fd3de7cb23fa4607c1a34c58ec417b305
--- /dev/null
+++ b/_examples/cluster-metrics/go.sum
@@ -0,0 +1,322 @@
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-metrics/server/main.go b/_examples/cluster-metrics/server/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a30254a5e2fa3bb78be9eb7e62956495ea88bb34
--- /dev/null
+++ b/_examples/cluster-metrics/server/main.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "cluster-metrics/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func Logger(next actor.ReceiverFunc) actor.ReceiverFunc {
+ fn := func(context actor.ReceiverContext, env *actor.MessageEnvelope) {
+ switch env.Message.(type) {
+ case *actor.Started:
+ log.Printf("actor started " + context.Self().String())
+ case *actor.Stopped:
+ log.Printf("actor stopped " + context.Self().String())
+ }
+ next(context, env)
+ }
+
+ return fn
+}
+
+func newHelloActor() actor.Actor {
+ return &shared.HelloActor{
+ Timeout: 20 * time.Second,
+ }
+}
+
+// a Go struct implementing the Hello interface
+type HelloGrain struct{}
+
+func (h *HelloGrain) Init(ctx cluster.GrainContext) {
+ log.Printf("new grain id=%s", ctx.Identity)
+}
+
+func (h *HelloGrain) Terminate(ctx cluster.GrainContext) {
+ log.Printf("delete grain id=%s", ctx.Identity())
+}
+
+func (*HelloGrain) ReceiveDefault(ctx cluster.GrainContext) {
+ msg := ctx.Message()
+ log.Printf("Unknown message %v", msg)
+}
+
+func (h *HelloGrain) SayHello(r *shared.HelloRequest, ctx cluster.GrainContext) (*shared.HelloResponse, error) {
+ return &shared.HelloResponse{Message: "hello " + r.Name + " from " + ctx.Identity()}, nil
+}
+
+func (*HelloGrain) Add(r *shared.AddRequest, ctx cluster.GrainContext) (*shared.AddResponse, error) {
+ return &shared.AddResponse{Result: r.A + r.B}, nil
+}
+
+func (*HelloGrain) VoidFunc(r *shared.AddRequest, ctx cluster.GrainContext) (*shared.Unit, error) {
+ return &shared.Unit{}, nil
+}
+
+func main() {
+ port := flag.Int("port", 0, "")
+ flag.Parse()
+ system := actor.NewActorSystem()
+ remoteConfig := remote.Configure("127.0.0.1", *port)
+ helloKind := shared.NewHelloKind(func() shared.Hello { return &HelloGrain{} }, 0, actor.WithReceiverMiddleware(Logger))
+ cluster.SetLogLevel(logmod.InfoLevel)
+
+ provider, _ := consul.New()
+ lookup := disthash.New()
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, remoteConfig, cluster.WithKinds(helloKind))
+ c := cluster.New(system, clusterConfig)
+ c.StartMember()
+
+ // this node knows about Hello kind
+ hello := shared.GetHelloGrainClient(c, "MyGrain")
+ msg := &shared.HelloRequest{Name: "Roger"}
+ res, err := hello.SayHello(msg)
+ if err != nil {
+ log.Fatalf("failed to call SayHello, err:%v", err)
+ }
+ log.Printf("Message from grain: %v", res.Message)
+ _, _ = console.ReadLine()
+ c.Shutdown(true)
+}
diff --git a/_examples/cluster-metrics/shared/build.sh b/_examples/cluster-metrics/shared/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..5fcdfc759c64741b07f04920edb13084d07da977
--- /dev/null
+++ b/_examples/cluster-metrics/shared/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-metrics/shared/protos.pb.go b/_examples/cluster-metrics/shared/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..0becc7923af02ed1d92756f70aad9292c895ef2d
--- /dev/null
+++ b/_examples/cluster-metrics/shared/protos.pb.go
@@ -0,0 +1,409 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package shared
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Unit struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Unit) Reset() {
+ *x = Unit{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Unit) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Unit) ProtoMessage() {}
+
+func (x *Unit) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Unit.ProtoReflect.Descriptor instead.
+func (*Unit) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type HelloRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *HelloRequest) Reset() {
+ *x = HelloRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type HelloResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (x *HelloResponse) Reset() {
+ *x = HelloResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResponse) ProtoMessage() {}
+
+func (x *HelloResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
+func (*HelloResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *HelloResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type AddRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ A float64 `protobuf:"fixed64,1,opt,name=a,proto3" json:"a,omitempty"`
+ B float64 `protobuf:"fixed64,2,opt,name=b,proto3" json:"b,omitempty"`
+}
+
+func (x *AddRequest) Reset() {
+ *x = AddRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AddRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddRequest) ProtoMessage() {}
+
+func (x *AddRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddRequest.ProtoReflect.Descriptor instead.
+func (*AddRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *AddRequest) GetA() float64 {
+ if x != nil {
+ return x.A
+ }
+ return 0
+}
+
+func (x *AddRequest) GetB() float64 {
+ if x != nil {
+ return x.B
+ }
+ return 0
+}
+
+type AddResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Result float64 `protobuf:"fixed64,1,opt,name=result,proto3" json:"result,omitempty"`
+}
+
+func (x *AddResponse) Reset() {
+ *x = AddResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AddResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddResponse) ProtoMessage() {}
+
+func (x *AddResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddResponse.ProtoReflect.Descriptor instead.
+func (*AddResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *AddResponse) GetResult() float64 {
+ if x != nil {
+ return x.Result
+ }
+ return 0
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x06, 0x0a, 0x04, 0x55, 0x6e, 0x69, 0x74, 0x22, 0x22,
+ 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,
+ 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+ 0x6d, 0x65, 0x22, 0x29, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x28, 0x0a,
+ 0x0a, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x61,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x61, 0x12, 0x0c, 0x0a, 0x01, 0x62, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x62, 0x22, 0x25, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x32, 0xa4,
+ 0x01, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48,
+ 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x14, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x48, 0x65,
+ 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61,
+ 0x72, 0x65, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x12, 0x12, 0x2e, 0x73, 0x68, 0x61,
+ 0x72, 0x65, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13,
+ 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x56, 0x6f, 0x69, 0x64, 0x46, 0x75, 0x6e,
+ 0x63, 0x12, 0x12, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x55,
+ 0x6e, 0x69, 0x74, 0x22, 0x00, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x6d, 0x65, 0x74,
+ 0x72, 0x69, 0x63, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_protos_proto_goTypes = []interface{}{
+ (*Unit)(nil), // 0: shared.Unit
+ (*HelloRequest)(nil), // 1: shared.HelloRequest
+ (*HelloResponse)(nil), // 2: shared.HelloResponse
+ (*AddRequest)(nil), // 3: shared.AddRequest
+ (*AddResponse)(nil), // 4: shared.AddResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 1, // 0: shared.Hello.SayHello:input_type -> shared.HelloRequest
+ 3, // 1: shared.Hello.Add:input_type -> shared.AddRequest
+ 3, // 2: shared.Hello.VoidFunc:input_type -> shared.AddRequest
+ 2, // 3: shared.Hello.SayHello:output_type -> shared.HelloResponse
+ 4, // 4: shared.Hello.Add:output_type -> shared.AddResponse
+ 0, // 5: shared.Hello.VoidFunc:output_type -> shared.Unit
+ 3, // [3:6] is the sub-list for method output_type
+ 0, // [0:3] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Unit); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AddRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AddResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 5,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-metrics/shared/protos.proto b/_examples/cluster-metrics/shared/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..f6bb8234b2b9daf5fe2c0eb378b4b9f385e3c5dd
--- /dev/null
+++ b/_examples/cluster-metrics/shared/protos.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-metrics/shared";
+package shared;
+
+message Unit {}
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloResponse {
+ string message = 1;
+}
+
+message AddRequest {
+ double a = 1;
+ double b = 2;
+}
+
+message AddResponse {
+ double result = 1;
+}
+
+service Hello {
+ rpc SayHello (HelloRequest) returns (HelloResponse) {}
+ rpc Add(AddRequest) returns (AddResponse) {}
+ rpc VoidFunc(AddRequest) returns (Unit) {}
+}
diff --git a/_examples/cluster-metrics/shared/protos_protoactor.go b/_examples/cluster-metrics/shared/protos_protoactor.go
new file mode 100644
index 0000000000000000000000000000000000000000..20ff065e50259e215d4b78959125c3079f659e34
--- /dev/null
+++ b/_examples/cluster-metrics/shared/protos_protoactor.go
@@ -0,0 +1,268 @@
+// Package shared is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package shared
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][shared]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+var xHelloFactory func() Hello
+
+// HelloFactory produces a Hello
+func HelloFactory(factory func() Hello) {
+ xHelloFactory = factory
+}
+
+// GetHelloGrainClient instantiates a new HelloGrainClient with given Identity
+func GetHelloGrainClient(c *cluster.Cluster, id string) *HelloGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &HelloGrainClient{Identity: id, cluster: c}
+}
+
+// GetHelloKind instantiates a new cluster.Kind for Hello
+func GetHelloKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &HelloActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Hello", props)
+ return kind
+}
+
+// GetHelloKind instantiates a new cluster.Kind for Hello
+func NewHelloKind(factory func() Hello, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xHelloFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &HelloActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Hello", props)
+ return kind
+}
+
+// Hello interfaces the services available to the Hello
+type Hello interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ SayHello(*HelloRequest, cluster.GrainContext) (*HelloResponse, error)
+ Add(*AddRequest, cluster.GrainContext) (*AddResponse, error)
+ VoidFunc(*AddRequest, cluster.GrainContext) (*Unit, error)
+}
+
+// HelloGrainClient holds the base data for the HelloGrain
+type HelloGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// SayHello requests the execution on to the cluster with CallOptions
+func (g *HelloGrainClient) SayHello(r *HelloRequest, opts ...cluster.GrainCallOption) (*HelloResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Hello", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &HelloResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// Add requests the execution on to the cluster with CallOptions
+func (g *HelloGrainClient) Add(r *AddRequest, opts ...cluster.GrainCallOption) (*AddResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Hello", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &AddResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// VoidFunc requests the execution on to the cluster with CallOptions
+func (g *HelloGrainClient) VoidFunc(r *AddRequest, opts ...cluster.GrainCallOption) (*Unit, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Hello", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &Unit{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// HelloActor represents the actor structure
+type HelloActor struct {
+ ctx cluster.GrainContext
+ inner Hello
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *HelloActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xHelloFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &HelloRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("SayHello(HelloRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.SayHello(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("SayHello(HelloRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 1:
+ req := &AddRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Add(AddRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Add(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Add(AddRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 2:
+ req := &AddRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("VoidFunc(AddRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.VoidFunc(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("VoidFunc(AddRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
diff --git a/_examples/cluster-pubsub-batching-producer/build.sh b/_examples/cluster-pubsub-batching-producer/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..d2d41b15f65732390fc14bc83e413e57c2584941
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/build.sh
@@ -0,0 +1 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
diff --git a/_examples/cluster-pubsub-batching-producer/go.mod b/_examples/cluster-pubsub-batching-producer/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..fbc265e0b9bd731c21b283304b8449a5a8b4d082
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/go.mod
@@ -0,0 +1,43 @@
+module cluster-pubsub-batching-provider
+
+go 1.18
+
+require (
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ gitee.com/simplexyz/simpleactor-go v0.0.0-20230121103438-4df724400d0f
+ google.golang.org/protobuf v1.28.1
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.0.53 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.12.2 // indirect
+ github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/common v0.34.0 // indirect
+ github.com/prometheus/procfs v0.7.3 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.7.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.30.0 // indirect
+ go.opentelemetry.io/otel/metric v0.30.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.7.0 // indirect
+ go.opentelemetry.io/otel/sdk/export/metric v0.28.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.30.0 // indirect
+ go.opentelemetry.io/otel/trace v1.7.0 // indirect
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
+ golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect
+ golang.org/x/sys v0.3.0 // indirect
+ golang.org/x/text v0.5.0 // indirect
+ google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect
+ google.golang.org/grpc v1.46.2 // indirect
+)
diff --git a/_examples/cluster-pubsub-batching-producer/go.work b/_examples/cluster-pubsub-batching-producer/go.work
new file mode 100644
index 0000000000000000000000000000000000000000..6195ec621a673819451273cb29f7a7ea6b96db49
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/go.work
@@ -0,0 +1,7 @@
+go 1.19
+
+use (
+ ../../../protoactor-go
+ .
+)
+
diff --git a/_examples/cluster-pubsub-batching-producer/main.go b/_examples/cluster-pubsub-batching-producer/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef7755640fa01a873a3ba3428a648b4236282394
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/main.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/test"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ c := startNode()
+
+ const topic = "my-topic"
+ var deliveredCount int32 = 0
+ for i := 0; i < 3; i++ {
+ _, _ = c.SubscribeWithReceive(topic, func(context actor.Context) {
+ switch context.Message().(type) {
+ case *PingMessage:
+ atomic.AddInt32(&deliveredCount, 1)
+ }
+ })
+ }
+ const count = 1_000_000
+ fmt.Println("Starting producer...")
+
+ producer := c.BatchingProducer(topic)
+
+ start := time.Now()
+
+ tasks := make([]*cluster.ProduceProcessInfo, count)
+ for i := 0; i < count; i++ {
+ info, err := producer.Produce(context.Background(), &PingMessage{Data: int32(i)})
+ if err != nil {
+ panic(err)
+ }
+ tasks[i] = info
+ }
+ for _, task := range tasks {
+ <-task.Finished
+ }
+ elapsed := time.Since(start)
+ producer.Dispose()
+ c.Shutdown(true)
+
+ fmt.Printf("Sent: %d, delivered: %d, msg/s %f\n", count, deliveredCount, float64(deliveredCount)/elapsed.Seconds())
+
+ console.ReadLine()
+}
+
+func startNode() *cluster.Cluster {
+ // how long before the grain poisons itself
+ system := actor.NewActorSystem()
+
+ provider := test.NewTestProvider(test.NewInMemAgent())
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+
+ cluster := cluster.New(system, clusterConfig)
+
+ cluster.StartMember()
+
+ return cluster
+}
diff --git a/_examples/cluster-pubsub-batching-producer/protos.pb.go b/_examples/cluster-pubsub-batching-producer/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..e3f2f2c766d51229bf71306003b0469389d87b3d
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/protos.pb.go
@@ -0,0 +1,146 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: protos.proto
+
+package main
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type PingMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Data int32 `protobuf:"varint,1,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *PingMessage) Reset() {
+ *x = PingMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PingMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PingMessage) ProtoMessage() {}
+
+func (x *PingMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PingMessage.ProtoReflect.Descriptor instead.
+func (*PingMessage) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *PingMessage) GetData() int32 {
+ if x != nil {
+ return x.Data
+ }
+ return 0
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04,
+ 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x21, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x05, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x53, 0x5a, 0x51, 0x67, 0x69, 0x74, 0x68, 0x75,
+ 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x70,
+ 0x75, 0x62, 0x73, 0x75, 0x62, 0x2d, 0x62, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x2d, 0x70,
+ 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_protos_proto_goTypes = []interface{}{
+ (*PingMessage)(nil), // 0: main.PingMessage
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PingMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-pubsub-batching-producer/protos.proto b/_examples/cluster-pubsub-batching-producer/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..28505ed03b1c004c2991e78151124dac5dfcfcc5
--- /dev/null
+++ b/_examples/cluster-pubsub-batching-producer/protos.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-pubsub-batching-producer/main";
+package main;
+
+message PingMessage {
+ int32 data = 1;
+}
diff --git a/_examples/cluster-pubsub/build.sh b/_examples/cluster-pubsub/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..5fcdfc759c64741b07f04920edb13084d07da977
--- /dev/null
+++ b/_examples/cluster-pubsub/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-pubsub/go.mod b/_examples/cluster-pubsub/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..cdf49ae8c25d5746731a99c00fec7633e1de9122
--- /dev/null
+++ b/_examples/cluster-pubsub/go.mod
@@ -0,0 +1,43 @@
+module cluster-pubsub
+
+go 1.18
+
+require (
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ gitee.com/simplexyz/simpleactor-go v0.0.0-20230121103438-4df724400d0f
+ google.golang.org/protobuf v1.28.1
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.0.53 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.12.2 // indirect
+ github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/common v0.34.0 // indirect
+ github.com/prometheus/procfs v0.7.3 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.7.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.30.0 // indirect
+ go.opentelemetry.io/otel/metric v0.30.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.7.0 // indirect
+ go.opentelemetry.io/otel/sdk/export/metric v0.28.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.30.0 // indirect
+ go.opentelemetry.io/otel/trace v1.7.0 // indirect
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
+ golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect
+ golang.org/x/sys v0.3.0 // indirect
+ golang.org/x/text v0.5.0 // indirect
+ google.golang.org/genproto v0.0.0-20220526192754-51939a95c655 // indirect
+ google.golang.org/grpc v1.46.2 // indirect
+)
diff --git a/_examples/cluster-pubsub/go.work b/_examples/cluster-pubsub/go.work
new file mode 100644
index 0000000000000000000000000000000000000000..6195ec621a673819451273cb29f7a7ea6b96db49
--- /dev/null
+++ b/_examples/cluster-pubsub/go.work
@@ -0,0 +1,7 @@
+go 1.19
+
+use (
+ ../../../protoactor-go
+ .
+)
+
diff --git a/_examples/cluster-pubsub/main.go b/_examples/cluster-pubsub/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8cc2d13b5db63670cd535d59d778036a055391e
--- /dev/null
+++ b/_examples/cluster-pubsub/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "strconv"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/test"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ c := startNode()
+
+ for i := 0; i < 3; i++ {
+ GetUserActorGrainClient(c, "user"+strconv.Itoa(i)).Connect(&Empty{})
+ }
+
+ console.ReadLine()
+ c.Shutdown(true)
+}
+
+func startNode() *cluster.Cluster {
+ // how long before the grain poisons itself
+ system := actor.NewActorSystem()
+
+ provider := test.NewTestProvider(test.NewInMemAgent())
+ lookup := disthash.New()
+ config := remote.Configure("localhost", 0)
+
+ userKind := NewUserActorKind(func() UserActor {
+ return &User{}
+ }, 0)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config,
+ cluster.WithKinds(userKind))
+
+ cluster := cluster.New(system, clusterConfig)
+
+ cluster.StartMember()
+
+ return cluster
+}
diff --git a/_examples/cluster-pubsub/protos.pb.go b/_examples/cluster-pubsub/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..a43d6a83cffaa1e8d78a551a0bbfe0efc5900592
--- /dev/null
+++ b/_examples/cluster-pubsub/protos.pb.go
@@ -0,0 +1,211 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: protos.proto
+
+package main
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ChatMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"`
+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
+}
+
+func (x *ChatMessage) Reset() {
+ *x = ChatMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ChatMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ChatMessage) ProtoMessage() {}
+
+func (x *ChatMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ChatMessage.ProtoReflect.Descriptor instead.
+func (*ChatMessage) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ChatMessage) GetSender() string {
+ if x != nil {
+ return x.Sender
+ }
+ return ""
+}
+
+func (x *ChatMessage) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type Empty struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Empty) Reset() {
+ *x = Empty{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Empty) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Empty) ProtoMessage() {}
+
+func (x *Empty) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
+func (*Empty) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04,
+ 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x3f, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0x30,
+ 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x07, 0x43,
+ 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x45, 0x6d,
+ 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
+ 0x42, 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61,
+ 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74,
+ 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x2f, 0x6d,
+ 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_protos_proto_goTypes = []interface{}{
+ (*ChatMessage)(nil), // 0: main.ChatMessage
+ (*Empty)(nil), // 1: main.Empty
+}
+var file_protos_proto_depIdxs = []int32{
+ 1, // 0: main.UserActor.Connect:input_type -> main.Empty
+ 1, // 1: main.UserActor.Connect:output_type -> main.Empty
+ 1, // [1:2] is the sub-list for method output_type
+ 0, // [0:1] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ChatMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Empty); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-pubsub/protos.proto b/_examples/cluster-pubsub/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..3f0902c8293b31543699e172c3632d988b122b2a
--- /dev/null
+++ b/_examples/cluster-pubsub/protos.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-pubsub/main";
+package main;
+
+message ChatMessage {
+ string sender = 1;
+ string message = 2;
+}
+
+message Empty {}
+
+service UserActor {
+ rpc Connect(Empty) returns (Empty);
+}
diff --git a/_examples/cluster-pubsub/protos_protoactor.go b/_examples/cluster-pubsub/protos_protoactor.go
new file mode 100644
index 0000000000000000000000000000000000000000..98a06e01b6dd45fce3ed9ec7bf4913f09605cafb
--- /dev/null
+++ b/_examples/cluster-pubsub/protos_protoactor.go
@@ -0,0 +1,165 @@
+// Package main is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package main
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][main]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+var xUserActorFactory func() UserActor
+
+// UserActorFactory produces a UserActor
+func UserActorFactory(factory func() UserActor) {
+ xUserActorFactory = factory
+}
+
+// GetUserActorGrainClient instantiates a new UserActorGrainClient with given Identity
+func GetUserActorGrainClient(c *cluster.Cluster, id string) *UserActorGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &UserActorGrainClient{Identity: id, cluster: c}
+}
+
+// GetUserActorKind instantiates a new cluster.Kind for UserActor
+func GetUserActorKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &UserActorActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("UserActor", props)
+ return kind
+}
+
+// GetUserActorKind instantiates a new cluster.Kind for UserActor
+func NewUserActorKind(factory func() UserActor, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xUserActorFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &UserActorActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("UserActor", props)
+ return kind
+}
+
+// UserActor interfaces the services available to the UserActor
+type UserActor interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ Connect(*Empty, cluster.GrainContext) (*Empty, error)
+}
+
+// UserActorGrainClient holds the base data for the UserActorGrain
+type UserActorGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// Connect requests the execution on to the cluster with CallOptions
+func (g *UserActorGrainClient) Connect(r *Empty, opts ...cluster.GrainCallOption) (*Empty, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "UserActor", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &Empty{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// UserActorActor represents the actor structure
+type UserActorActor struct {
+ ctx cluster.GrainContext
+ inner UserActor
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *UserActorActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xUserActorFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &Empty{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Connect(Empty) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Connect(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Connect(Empty) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
diff --git a/_examples/cluster-pubsub/user.go b/_examples/cluster-pubsub/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..61a1f7b0a1608512fa5106e12df2a51542793218
--- /dev/null
+++ b/_examples/cluster-pubsub/user.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "math/rand"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/scheduler"
+)
+
+const Topic = "chat"
+
+var messages = []string{
+ "Good day sir!",
+ "Lovely weather, innit?",
+ "How do you do?",
+ "Pardon me!",
+}
+
+func randomMessage() string {
+ return messages[rand.Intn(len(messages))]
+}
+
+type Tick struct{}
+
+type User struct {
+ cancelTick scheduler.CancelFunc
+}
+
+func (u *User) Init(ctx cluster.GrainContext) {
+}
+
+func (u *User) Terminate(ctx cluster.GrainContext) {
+ if u.cancelTick != nil {
+ u.cancelTick()
+ }
+}
+
+func (u *User) ReceiveDefault(ctx cluster.GrainContext) {
+ switch msg := ctx.Message().(type) {
+ case *Tick:
+ chatMsg := randomMessage()
+ fmt.Printf("[SEND] User %s says: %s\n", ctx.Identity(), chatMsg)
+ _, err := ctx.Cluster().Publisher().Publish(context.Background(), Topic, &ChatMessage{Message: chatMsg, Sender: ctx.Identity()})
+ if err != nil {
+ fmt.Println(err)
+ }
+ case *ChatMessage:
+ fmt.Printf("[RECEIVED] User %s received %s from %s\n", ctx.Identity(), msg.Message, msg.Sender)
+ }
+}
+
+func (u *User) Connect(_ *Empty, context cluster.GrainContext) (*Empty, error) {
+ s := scheduler.NewTimerScheduler(context)
+ interval := time.Second * time.Duration(rand.Intn(4)+2)
+ u.cancelTick = s.SendRepeatedly(interval, interval, context.Self(), &Tick{})
+
+ _, err := context.Cluster().SubscribeByPid(Topic, context.Self())
+ return &Empty{}, err
+}
diff --git a/_examples/cluster-restartgracefully/Makefile b/_examples/cluster-restartgracefully/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0676a8202166ba608f387e86ee56dab103a673d1
--- /dev/null
+++ b/_examples/cluster-restartgracefully/Makefile
@@ -0,0 +1,46 @@
+cp:=consul
+ttl:=10s
+loops:=10000
+clients:=10
+interval:=0ms
+env=prod
+
+start:
+ tmux new-session -d -s eg
+ tmux setenv -t eg PROTO_ACTOR_ENV $(env)
+ tmux split-window -t "eg:0" -v
+ tmux split-window -t "eg:0.0" -h -p 66
+ tmux split-window -t "eg:0.1" -h -p 50
+ tmux select-pane -t "eg:0.3"
+ tmux send-keys -t "eg:0.0" "go run server/main.go --provider $(cp) --ttl $(ttl) --port 9991" Enter
+ tmux send-keys -t "eg:0.1" "go run server/main.go --provider $(cp) --ttl $(ttl) --port 9992" Enter
+ tmux send-keys -t "eg:0.2" "go run server/main.go --provider $(cp) --ttl $(ttl) --port 9993" Enter
+ tmux send-keys -t "eg:0.3" "sleep 2 && go run client/main.go --provider $(cp) --clients $(clients) --loops $(loops) --interval $(interval) " Enter
+ # tmux send-keys -t "eg:0.2" "go run member/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+start-with-etcd:
+ make start cp=etcd
+
+
+debug:
+ PROTO_ACTOR_ENV=dev make start cp=etcd
+
+
+mock-clients-10w:
+ make start cp=etcd clients=100000 loops=10 interval=100ms
+
+
+mock-clients-20w:
+ make start cp=etcd clients=200000 loops=10 interval=100ms
+
+
+stop:
+ tmux kill-session -t eg
+
+
+proto:
+ protoc -I=. --gogoslick_out=. shared/*.proto
+ protoc -I=. --gograinv2_out=. shared/*.proto
diff --git a/_examples/cluster-restartgracefully/cache/cache.go b/_examples/cluster-restartgracefully/cache/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..abbfa35b3ea5ce0b453496ff03728e836211f620
--- /dev/null
+++ b/_examples/cluster-restartgracefully/cache/cache.go
@@ -0,0 +1,37 @@
+package cache
+
+import (
+ "log"
+ "time"
+
+ "github.com/go-redis/redis"
+)
+
+var rds *redis.Client
+
+func init() {
+ // var err error
+ addr := "127.0.0.1:6379"
+ rds = redis.NewClient(&redis.Options{
+ Addr: addr,
+ DialTimeout: 1 * time.Second,
+ })
+ if err := rds.Ping().Err(); err != nil {
+ log.Printf("no redis err=%v", err)
+ }
+}
+
+func GetCountor(key string) int64 {
+ v, err := rds.Get(key).Int64()
+ if err != nil && err != redis.Nil {
+ panic(err)
+ }
+ return v
+}
+
+func SetCountor(key string, val int64) {
+ err := rds.Set(key, val, time.Hour).Err()
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/_examples/cluster-restartgracefully/client/main.go b/_examples/cluster-restartgracefully/client/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..09383a40052d73ff075e983c3415f4f86dea7894
--- /dev/null
+++ b/_examples/cluster-restartgracefully/client/main.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "sync"
+ "time"
+
+ "cluster-restartgracefully/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/partition"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ plog = log.New(log.DebugLevel, "[Example]")
+ _cluster *cluster.Cluster
+)
+
+func main() {
+ cluster.SetLogLevel(log.InfoLevel)
+ loops := flag.Int("loops", 10000, "request times.")
+ interval := flag.Duration("interval", 0, "request interval miliseconds per client.")
+ clients := flag.Int("clients", 1, "clients count.")
+ provider := flag.String("provider", "consul", "clients count.")
+ flag.Parse()
+
+ // start server
+ startNode(0, *provider)
+ for {
+ runClientsAll(*clients, *loops, *interval)
+ plog.Info("countinue? (y/n)")
+ cmd, err := console.ReadLine()
+ if err != nil {
+ panic(err)
+ }
+ if cmd == "n" || cmd == "quit" {
+ break
+ }
+ }
+ plog.Info("shutdown ...")
+ _cluster.Shutdown(true)
+ plog.Info("shutdown OK")
+}
+
+func startNode(port int, provider string) {
+ var cp cluster.ClusterProvider
+ var err error
+ switch provider {
+ case "consul":
+ ttl := consul.WithTTL(100 * time.Millisecond)
+ refreshTTL := consul.WithRefreshTTL(100 * time.Millisecond)
+ cp, err = consul.New(ttl, refreshTTL)
+ // case "etcd":
+ // cp, err = etcd.New()
+ default:
+ panic(fmt.Errorf("invalid provider:%s", provider))
+ }
+
+ if err != nil {
+ panic(err)
+ }
+
+ id := partition.New()
+ remoteCfg := remote.Configure("127.0.0.1", port)
+ cfg := cluster.Configure("cluster-restartgracefully", cp, id, remoteCfg)
+ _cluster = cluster.New(system, cfg)
+ _cluster.StartClient()
+}
+
+func runClientsAll(clients int, loops int, interval time.Duration) {
+ var wg sync.WaitGroup
+ now := time.Now()
+ for i := 0; i < clients; i++ {
+ wg.Add(1)
+ grainId := fmt.Sprintf("client-%d", i)
+ go func() {
+ runClient(grainId, loops, interval)
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ cost := time.Since(now)
+ total := clients * loops
+ costSecs := int(cost / time.Second)
+ if costSecs <= 0 {
+ costSecs = 1
+ }
+ plog.Info("end all.",
+ log.Int("clients", clients),
+ log.Int("total", total),
+ log.Int("req/s", total/costSecs),
+ log.Duration("take", cost))
+}
+
+func runClient(grainId string, loops int, interval time.Duration) {
+ now := time.Now()
+ calcGrain := shared.GetCalculatorGrainClient(_cluster, grainId)
+ resp, err := calcGrain.GetCurrent(&shared.Void{}, cluster.WithRetry(3), cluster.WithTimeout(6*time.Second))
+ if err != nil {
+ _cluster.Shutdown(true)
+ panic(err)
+ }
+ baseNumber := resp.Number
+ plog.Info("requests",
+ log.String("grainId", grainId),
+ log.String("status", "start"))
+ for i := 1; i <= loops; i++ {
+ assert_calcAdd(grainId, 1, baseNumber+int64(i))
+ time.Sleep(interval)
+ }
+ plog.Info("requests",
+ log.String("grainId", grainId),
+ log.String("status", "end"),
+ log.Int("loops", loops),
+ log.Duration("take", time.Since(now)))
+}
+
+func calcAdd(grainId string, addNumber int64) int64 {
+ calcGrain := shared.GetCalculatorGrainClient(_cluster, grainId)
+ resp, err := calcGrain.Add(&shared.NumberRequest{Number: addNumber}, cluster.WithRetry(3), cluster.WithTimeout(6*time.Second))
+ if err != nil {
+ plog.Error("call grain failed", log.Error(err))
+ }
+ return resp.Number
+}
+
+func assert_calcAdd(grainId string, inc, expectedNumber int64) {
+ number := calcAdd(grainId, inc)
+ if number != expectedNumber {
+ err := fmt.Errorf("grainId:%s inc:%d number:%d (expected=%d)",
+ grainId, inc, number, expectedNumber)
+ panic(err)
+ }
+}
diff --git a/_examples/cluster-restartgracefully/client/main_test.go b/_examples/cluster-restartgracefully/client/main_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c0d6d309c8af24a0389495bd5322934d018b235e
--- /dev/null
+++ b/_examples/cluster-restartgracefully/client/main_test.go
@@ -0,0 +1,12 @@
+package main
+
+import "testing"
+
+func BenchmarkCalcAdd(t *testing.B) {
+ startNode(0, "consul")
+ calcAdd("yes", 1)
+ t.ResetTimer()
+ for i := 0; i < t.N; i++ {
+ calcAdd("yes", 1)
+ }
+}
diff --git a/_examples/cluster-restartgracefully/docker-compose.yml b/_examples/cluster-restartgracefully/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bcbc8627270e63a435ea9213133f3ad1f24e31f4
--- /dev/null
+++ b/_examples/cluster-restartgracefully/docker-compose.yml
@@ -0,0 +1,7 @@
+# @see https://docs.docker.com/compose/compose-file/
+version: '3.4'
+services:
+ redis:
+ image: redis:5.0.7-alpine
+ ports:
+ - 127.0.0.1:6380:6379
diff --git a/_examples/cluster-restartgracefully/go.mod b/_examples/cluster-restartgracefully/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..0b7668fe55cb03a278113455d3f9771fd74ff465
--- /dev/null
+++ b/_examples/cluster-restartgracefully/go.mod
@@ -0,0 +1,63 @@
+module cluster-restartgracefully
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ github.com/go-redis/redis v6.15.9+incompatible
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/btree v1.0.1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/consul/api v1.18.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/onsi/ginkgo v1.16.4 // indirect
+ github.com/onsi/gomega v1.23.0 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/cluster-restartgracefully/go.sum b/_examples/cluster-restartgracefully/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..0cbfbd8542aae6f373aa42963390282f7a8666f9
--- /dev/null
+++ b/_examples/cluster-restartgracefully/go.sum
@@ -0,0 +1,368 @@
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
+github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
+github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/cluster-restartgracefully/server/calculator-grain.go b/_examples/cluster-restartgracefully/server/calculator-grain.go
new file mode 100644
index 0000000000000000000000000000000000000000..4aef24492813f373aac9cd4964dedb2af573a98d
--- /dev/null
+++ b/_examples/cluster-restartgracefully/server/calculator-grain.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "cluster-restartgracefully/cache"
+ "cluster-restartgracefully/shared"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+type CalcGrain struct {
+ total int64
+}
+
+func (c *CalcGrain) Init(ctx cluster.GrainContext) {
+ c.total = cache.GetCountor(ctx.Identity())
+ plog.Info("start", log.String("id", ctx.Identity()), log.Int64("number", c.total))
+}
+
+func (c *CalcGrain) Terminate(ctx cluster.GrainContext) {
+ id := ctx.Identity()
+ cache.SetCountor(id, c.total)
+ plog.Info("stop", log.String("id", id), log.Int64("number", c.total))
+}
+
+func (c *CalcGrain) ReceiveDefault(ctx cluster.GrainContext) {
+}
+
+func (c *CalcGrain) Add(n *shared.NumberRequest, ctx cluster.GrainContext) (*shared.CountResponse, error) {
+ c.total = c.total + n.Number
+ return &shared.CountResponse{Number: c.total}, nil
+}
+
+func (c *CalcGrain) Subtract(n *shared.NumberRequest, ctx cluster.GrainContext) (*shared.CountResponse, error) {
+ c.total = c.total - n.Number
+ return &shared.CountResponse{Number: c.total}, nil
+}
+
+func (c *CalcGrain) GetCurrent(n *shared.Void, ctx cluster.GrainContext) (*shared.CountResponse, error) {
+ return &shared.CountResponse{Number: c.total}, nil
+}
diff --git a/_examples/cluster-restartgracefully/server/main.go b/_examples/cluster-restartgracefully/server/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e4b724c941da8961320f7efdc0967cc2c425967
--- /dev/null
+++ b/_examples/cluster-restartgracefully/server/main.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "cluster-restartgracefully/shared"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/consul"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+var (
+ plog = log.New(log.DebugLevel, "[Example]")
+ system = actor.NewActorSystem()
+ _cluster *cluster.Cluster
+)
+
+func main() {
+ provider := flag.String("provider", "consul", "clients count.")
+ actorTTL := flag.Duration("ttl", 10*time.Second, "time to live of actor.")
+ port := flag.Int("port", 0, "listen port.")
+
+ flag.Parse()
+ startNode(*port, *provider, *actorTTL)
+
+ // waiting CTRL-C
+ sigCh := make(chan os.Signal)
+ signal.Notify(sigCh, syscall.SIGINT)
+ for sig := range sigCh {
+ switch sig {
+ case syscall.SIGINT:
+ plog.Info("Shutdown...")
+ _cluster.Shutdown(true)
+ plog.Info("Shutdown ok")
+ time.Sleep(time.Second)
+ os.Exit(0)
+ default:
+ plog.Info("Skipping", log.Object("sig", sig))
+ }
+ }
+}
+
+func startNode(port int, provider string, timeout time.Duration) {
+ plog.Info("press 'CTRL-C' to shutdown server.")
+ shared.CalculatorFactory(func() shared.Calculator {
+ return &CalcGrain{}
+ })
+
+ var cp cluster.ClusterProvider
+ var err error
+ switch provider {
+ case "consul":
+ cp, err = consul.New()
+ // case "etcd":
+ // cp, err = etcd.New()
+ default:
+ panic(fmt.Errorf("invalid provider:%s", provider))
+ }
+
+ id := disthash.New()
+
+ if err != nil {
+ panic(err)
+ }
+
+ remoteCfg := remote.Configure("127.0.0.1", port)
+ cfg := cluster.Configure("cluster-restartgracefully", cp, id, remoteCfg, cluster.WithKinds(shared.GetCalculatorKind()))
+ _cluster = cluster.New(system, cfg)
+ _cluster.StartMember()
+}
diff --git a/_examples/cluster-restartgracefully/shared/build.sh b/_examples/cluster-restartgracefully/shared/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..5fcdfc759c64741b07f04920edb13084d07da977
--- /dev/null
+++ b/_examples/cluster-restartgracefully/shared/build.sh
@@ -0,0 +1,2 @@
+protoc --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+protoc -I=. --gograinv2_out=. protos.proto
diff --git a/_examples/cluster-restartgracefully/shared/protos.pb.go b/_examples/cluster-restartgracefully/shared/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..46245d446a13c68f4c37f41a5720f1050abb7934
--- /dev/null
+++ b/_examples/cluster-restartgracefully/shared/protos.pb.go
@@ -0,0 +1,277 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package shared
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Void struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Void) Reset() {
+ *x = Void{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Void) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Void) ProtoMessage() {}
+
+func (x *Void) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Void.ProtoReflect.Descriptor instead.
+func (*Void) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type NumberRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *NumberRequest) Reset() {
+ *x = NumberRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NumberRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NumberRequest) ProtoMessage() {}
+
+func (x *NumberRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NumberRequest.ProtoReflect.Descriptor instead.
+func (*NumberRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *NumberRequest) GetNumber() int64 {
+ if x != nil {
+ return x.Number
+ }
+ return 0
+}
+
+type CountResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Number int64 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *CountResponse) Reset() {
+ *x = CountResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *CountResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CountResponse) ProtoMessage() {}
+
+func (x *CountResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use CountResponse.ProtoReflect.Descriptor instead.
+func (*CountResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CountResponse) GetNumber() int64 {
+ if x != nil {
+ return x.Number
+ }
+ return 0
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x22, 0x06, 0x0a, 0x04, 0x56, 0x6f, 0x69, 0x64, 0x22, 0x27,
+ 0x0a, 0x0d, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
+ 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x27, 0x0a, 0x0d, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62,
+ 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+ 0x32, 0xb4, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x6f, 0x72, 0x12,
+ 0x35, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x12, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e,
+ 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e,
+ 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x08, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61,
+ 0x63, 0x74, 0x12, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x4e, 0x75, 0x6d, 0x62,
+ 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x68, 0x61, 0x72,
+ 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+ 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
+ 0x12, 0x0c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x1a, 0x15,
+ 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x4e, 0x5a, 0x4c, 0x67, 0x69, 0x74, 0x68, 0x75,
+ 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x72,
+ 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x67, 0x72, 0x61, 0x63, 0x65, 0x66, 0x75, 0x6c, 0x6c, 0x79,
+ 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_protos_proto_goTypes = []interface{}{
+ (*Void)(nil), // 0: shared.Void
+ (*NumberRequest)(nil), // 1: shared.NumberRequest
+ (*CountResponse)(nil), // 2: shared.CountResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 1, // 0: shared.Calculator.Add:input_type -> shared.NumberRequest
+ 1, // 1: shared.Calculator.Subtract:input_type -> shared.NumberRequest
+ 0, // 2: shared.Calculator.GetCurrent:input_type -> shared.Void
+ 2, // 3: shared.Calculator.Add:output_type -> shared.CountResponse
+ 2, // 4: shared.Calculator.Subtract:output_type -> shared.CountResponse
+ 2, // 5: shared.Calculator.GetCurrent:output_type -> shared.CountResponse
+ 3, // [3:6] is the sub-list for method output_type
+ 0, // [0:3] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Void); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NumberRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*CountResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 3,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/cluster-restartgracefully/shared/protos.proto b/_examples/cluster-restartgracefully/shared/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..1de7b24b3deeaa69ad97eba5682d7c142f5f5cab
--- /dev/null
+++ b/_examples/cluster-restartgracefully/shared/protos.proto
@@ -0,0 +1,19 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/cluster-restartgracefully/shared";
+package shared;
+
+message Void {}
+
+message NumberRequest {
+ int64 number = 1;
+}
+
+message CountResponse {
+ int64 number = 1;
+}
+
+service Calculator {
+ rpc Add(NumberRequest) returns (CountResponse) {}
+ rpc Subtract(NumberRequest) returns (CountResponse) {}
+ rpc GetCurrent(Void) returns (CountResponse) {}
+}
diff --git a/_examples/cluster-restartgracefully/shared/protos_protoactor.go b/_examples/cluster-restartgracefully/shared/protos_protoactor.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cef46abc580fc74ac371a1488da61754f3c0f5c
--- /dev/null
+++ b/_examples/cluster-restartgracefully/shared/protos_protoactor.go
@@ -0,0 +1,268 @@
+// Package shared is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package shared
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][shared]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+var xCalculatorFactory func() Calculator
+
+// CalculatorFactory produces a Calculator
+func CalculatorFactory(factory func() Calculator) {
+ xCalculatorFactory = factory
+}
+
+// GetCalculatorGrainClient instantiates a new CalculatorGrainClient with given Identity
+func GetCalculatorGrainClient(c *cluster.Cluster, id string) *CalculatorGrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &CalculatorGrainClient{Identity: id, cluster: c}
+}
+
+// GetCalculatorKind instantiates a new cluster.Kind for Calculator
+func GetCalculatorKind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &CalculatorActor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Calculator", props)
+ return kind
+}
+
+// GetCalculatorKind instantiates a new cluster.Kind for Calculator
+func NewCalculatorKind(factory func() Calculator, timeout time.Duration, opts ...actor.PropsOption) *cluster.Kind {
+ xCalculatorFactory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &CalculatorActor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("Calculator", props)
+ return kind
+}
+
+// Calculator interfaces the services available to the Calculator
+type Calculator interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ Add(*NumberRequest, cluster.GrainContext) (*CountResponse, error)
+ Subtract(*NumberRequest, cluster.GrainContext) (*CountResponse, error)
+ GetCurrent(*Void, cluster.GrainContext) (*CountResponse, error)
+}
+
+// CalculatorGrainClient holds the base data for the CalculatorGrain
+type CalculatorGrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+
+// Add requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) Add(r *NumberRequest, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 0, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// Subtract requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) Subtract(r *NumberRequest, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 1, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// GetCurrent requests the execution on to the cluster with CallOptions
+func (g *CalculatorGrainClient) GetCurrent(r *Void, opts ...cluster.GrainCallOption) (*CountResponse, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: 2, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "Calculator", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &CountResponse{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+
+// CalculatorActor represents the actor structure
+type CalculatorActor struct {
+ ctx cluster.GrainContext
+ inner Calculator
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *CalculatorActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: // pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = xCalculatorFactory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ case 0:
+ req := &NumberRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Add(NumberRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Add(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Add(NumberRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 1:
+ req := &NumberRequest{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("Subtract(NumberRequest) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.Subtract(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("Subtract(NumberRequest) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ case 2:
+ req := &Void{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("GetCurrent(Void) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.GetCurrent(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("GetCurrent(Void) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
diff --git a/_examples/kubernetes-sample/.dockerignore b/_examples/kubernetes-sample/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..b80d55cd3e0d61d7a6244a5ff0e04a7a71ce3c19
--- /dev/null
+++ b/_examples/kubernetes-sample/.dockerignore
@@ -0,0 +1,2 @@
+.git
+debug
\ No newline at end of file
diff --git a/_examples/kubernetes-sample/Dockerfile b/_examples/kubernetes-sample/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..6390ff49a9e0cbcd8fd32825f24a433dbd7f420d
--- /dev/null
+++ b/_examples/kubernetes-sample/Dockerfile
@@ -0,0 +1,11 @@
+FROM golang:1.19-alpine as build
+
+WORKDIR /src
+COPY . ./
+
+WORKDIR /src/_examples/kubernetes-sample
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -o app
+
+FROM scratch
+COPY --from=build /src/_examples/kubernetes-sample/app /
+ENTRYPOINT ["/app"]
\ No newline at end of file
diff --git a/_examples/kubernetes-sample/Dockerfile-localcompile b/_examples/kubernetes-sample/Dockerfile-localcompile
new file mode 100644
index 0000000000000000000000000000000000000000..92fcce82c1d2c5626644e964ba3ae131fdc51e3a
--- /dev/null
+++ b/_examples/kubernetes-sample/Dockerfile-localcompile
@@ -0,0 +1,3 @@
+FROM scratch
+COPY app /
+ENTRYPOINT ["/app"]
\ No newline at end of file
diff --git a/_examples/kubernetes-sample/Makefile b/_examples/kubernetes-sample/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e92db6deda42481ae165c59eaf3efb20e5b6a65c
--- /dev/null
+++ b/_examples/kubernetes-sample/Makefile
@@ -0,0 +1,16 @@
+.PHONY: docker docker-local deploy delete redeploy
+
+docker:
+ cd ../.. && docker build -f _examples/kubernetes-sample/Dockerfile -t kubernetes-sample:latest .
+
+docker-localcompile:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -o app
+ docker build -f Dockerfile-localcompile -t kubernetes-sample:latest .
+
+deploy:
+ kubectl apply -f deploy-sample.yaml
+
+delete:
+ kubectl delete -f deploy-sample.yaml
+
+redeploy: | delete deploy
\ No newline at end of file
diff --git a/_examples/kubernetes-sample/deploy-sample.yaml b/_examples/kubernetes-sample/deploy-sample.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c82c9d18a32ae3d7df0e8f8353cac76f44de6514
--- /dev/null
+++ b/_examples/kubernetes-sample/deploy-sample.yaml
@@ -0,0 +1,72 @@
+kind: Namespace
+apiVersion: v1
+metadata:
+ name: kubernetes-sample
+ labels:
+ name: kubernetes-sample
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: kubernetes-sample
+ namespace: kubernetes-sample
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: kubernetes-sample
+ namespace: kubernetes-sample
+rules:
+ - apiGroups:
+ - ""
+ resources:
+ - pods
+ verbs:
+ - get
+ - list
+ - watch
+ - patch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: kubernetes-sample
+ namespace: kubernetes-sample
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: kubernetes-sample
+subjects:
+ - kind: ServiceAccount
+ name: kubernetes-sample
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: kubernetes-sample
+ namespace: kubernetes-sample
+ labels:
+ app: kubernetes-sample
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: kubernetes-sample
+ template:
+ metadata:
+ namespace: kubernetes-sample
+ labels:
+ app: kubernetes-sample
+ spec:
+ serviceAccountName: kubernetes-sample
+ containers:
+ - name: kubernetes-sample
+ image: kubernetes-sample:latest
+ imagePullPolicy: Never # docker desktop
+ env:
+ - name: PROTOHOST
+ valueFrom:
+ fieldRef:
+ fieldPath: status.podIP
+ - name: PROTOPORT
+ value: "50051"
diff --git a/_examples/kubernetes-sample/go.mod b/_examples/kubernetes-sample/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..40e26f4f6e1861fa4c917b932b8eeadf36a6df06
--- /dev/null
+++ b/_examples/kubernetes-sample/go.mod
@@ -0,0 +1,75 @@
+module kubernetes-sample
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ google.golang.org/protobuf v1.31.0
+ k8s.io/utils v0.0.0-20221107191617-1a15be271d1d
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.9.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/swag v0.21.1 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/gnostic v0.6.9 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/oauth2 v0.10.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/term v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/api v0.26.1 // indirect
+ k8s.io/apimachinery v0.26.1 // indirect
+ k8s.io/client-go v0.26.1 // indirect
+ k8s.io/klog/v2 v2.80.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
+ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
+)
diff --git a/_examples/kubernetes-sample/go.sum b/_examples/kubernetes-sample/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..fd10341cbf7d00490c50267f42ddac24fcfc1b15
--- /dev/null
+++ b/_examples/kubernetes-sample/go.sum
@@ -0,0 +1,324 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
+github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
+github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
+github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
+k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
+k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
+k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
+k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
+k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
+k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/_examples/kubernetes-sample/main.go b/_examples/kubernetes-sample/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5b1addf40ebf7fb02ffbfb76f411e76d3a5076f
--- /dev/null
+++ b/_examples/kubernetes-sample/main.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/k8s"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "k8s.io/utils/env"
+)
+
+func main() {
+ log.Printf("Starting node\n")
+
+ c := startNode()
+ defer c.Shutdown(true)
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer stop()
+
+ sendMessages(ctx, c)
+
+ log.Printf("Shutting down\n")
+}
+
+func sendMessages(ctx context.Context, c *cluster.Cluster) {
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(5 * time.Second):
+ if _, err := c.Call(
+ "some-actor-123",
+ "helloKind",
+ &HelloRequest{Name: fmt.Sprintf("Hello from %s", c.ActorSystem.ID)}); err != nil {
+ log.Printf("Error calling actor: %v\n", err)
+ } else {
+ log.Printf("Successfully called actor\n")
+ }
+ }
+ }
+}
+
+func helloGrainReceive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *HelloRequest:
+ log.Printf("Got hello %v\n", msg)
+ ctx.Respond(&HelloResponse{})
+ }
+}
+
+func startNode() *cluster.Cluster {
+ host, port, advertisedHost := getHostInformation()
+
+ system := actor.NewActorSystem()
+ provider, err := k8s.New()
+ if err != nil {
+ log.Panic(err)
+ }
+ lookup := disthash.New()
+
+ config := remote.Configure(host, port, remote.WithAdvertisedHost(advertisedHost))
+
+ props := actor.PropsFromFunc(helloGrainReceive)
+ helloKind := cluster.NewKind("helloKind", props)
+
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config, cluster.WithKinds(helloKind))
+
+ c := cluster.New(system, clusterConfig)
+ c.StartMember()
+
+ return c
+}
+
+func getHostInformation() (host string, port int, advertisedHost string) {
+ host = env.GetString("PROTOHOST", "127.0.0.1")
+ port, err := env.GetInt("PROTOPORT", 0)
+ if err != nil {
+ log.Panic(err)
+ }
+ advertisedHost = env.GetString("PROTOADVERTISEDHOST", "")
+
+ log.Printf("host: %s, port: %d, advertisedHost: %s", host, port, advertisedHost)
+
+ return
+}
diff --git a/_examples/kubernetes-sample/protos.pb.go b/_examples/kubernetes-sample/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..a01c847c3739f7a6ae7d44e221226472ca328144
--- /dev/null
+++ b/_examples/kubernetes-sample/protos.pb.go
@@ -0,0 +1,197 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.26.0
+// protoc v3.20.2
+// source: protos.proto
+
+package main
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HelloRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
+}
+
+func (x *HelloRequest) Reset() {
+ *x = HelloRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *HelloRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+type HelloResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *HelloResponse) Reset() {
+ *x = HelloResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResponse) ProtoMessage() {}
+
+func (x *HelloResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
+func (*HelloResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04,
+ 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71,
+ 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c,
+ 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e,
+ 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65,
+ 0x74, 0x65, 0x73, 0x2d, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62,
+ 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_protos_proto_goTypes = []interface{}{
+ (*HelloRequest)(nil), // 0: main.HelloRequest
+ (*HelloResponse)(nil), // 1: main.HelloResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/kubernetes-sample/protos.proto b/_examples/kubernetes-sample/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..178e82253427074e07879ba17428d646b097c1ef
--- /dev/null
+++ b/_examples/kubernetes-sample/protos.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+package main;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/kubernetes-sample/main";
+
+message HelloRequest {
+ string Name = 1;
+}
+
+message HelloResponse { }
\ No newline at end of file
diff --git a/_examples/opentelemetry-custom-provider/go.mod b/_examples/opentelemetry-custom-provider/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a2dd0cac21fb0b608457e6371cd00cd97bf1b2a3
--- /dev/null
+++ b/_examples/opentelemetry-custom-provider/go.mod
@@ -0,0 +1,15 @@
+module opentelemetry-custom-provider
+
+go 1.21
+
+toolchain go1.21.3
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.27.0
+ go.opentelemetry.io/otel/metric v1.16.0
+ go.opentelemetry.io/otel/sdk/metric v0.39.0
+)
diff --git a/_examples/opentelemetry-custom-provider/go.sum b/_examples/opentelemetry-custom-provider/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..b73ee5525497bd36b22289b181b8e9c71384b4ce
--- /dev/null
+++ b/_examples/opentelemetry-custom-provider/go.sum
@@ -0,0 +1,1794 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
+cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
+cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
+cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
+cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
+cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
+cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=
+cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=
+cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=
+cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=
+cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=
+cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=
+cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=
+cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=
+cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=
+cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=
+cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY=
+cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM=
+cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU=
+cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI=
+cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=
+cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=
+cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=
+cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=
+cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
+cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=
+cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=
+cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=
+cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
+cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
+cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
+cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=
+cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=
+cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=
+cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=
+cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=
+cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=
+cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=
+cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=
+cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=
+cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=
+cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=
+cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=
+cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=
+cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=
+cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=
+cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=
+cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=
+cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=
+cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=
+cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=
+cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=
+cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=
+cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=
+cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=
+cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=
+cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=
+cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=
+cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=
+cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
+cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=
+cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=
+cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
+cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
+cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=
+cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
+cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
+cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
+cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
+cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=
+cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=
+cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=
+cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=
+cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M=
+cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=
+cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA=
+cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=
+cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=
+cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=
+cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=
+cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=
+cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ=
+cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=
+cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=
+cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
+cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=
+cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=
+cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs=
+cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=
+cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=
+cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=
+cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=
+cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=
+cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM=
+cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4=
+cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=
+cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=
+cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=
+cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=
+cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
+cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc=
+cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
+cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=
+cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=
+cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=
+cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=
+cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=
+cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw=
+cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=
+cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=
+cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs=
+cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=
+cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=
+cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=
+cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=
+cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=
+cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=
+cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=
+cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E=
+cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=
+cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=
+cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=
+cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=
+cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=
+cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
+cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
+cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
+cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=
+cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=
+cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=
+cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
+cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=
+cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=
+cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=
+cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=
+cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=
+cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=
+cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o=
+cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
+cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
+cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
+cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
+cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=
+cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=
+cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
+cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
+cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
+cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
+cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
+cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=
+cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
+cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=
+cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=
+cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=
+cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=
+cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=
+cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=
+cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=
+cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=
+cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
+cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
+cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=
+cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E=
+cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=
+cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=
+cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=
+cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=
+cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE=
+cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=
+cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=
+cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=
+cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=
+cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=
+cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=
+cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=
+cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=
+cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=
+cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=
+cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=
+cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=
+cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=
+cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=
+cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=
+cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
+cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=
+cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
+cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=
+cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=
+cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=
+cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=
+cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=
+cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=
+cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=
+cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=
+cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=
+cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=
+cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=
+cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=
+cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=
+cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=
+cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=
+cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=
+cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=
+cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=
+cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=
+cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=
+cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=
+cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=
+cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=
+cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=
+cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=
+cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=
+cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=
+cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=
+cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=
+cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=
+cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=
+cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=
+cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=
+cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY=
+cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=
+cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=
+cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc=
+cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=
+cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=
+cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec=
+cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=
+cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=
+cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=
+cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
+cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
+cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
+cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=
+cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
+cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
+cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
+cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
+cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=
+cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=
+cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=
+cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=
+cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=
+cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=
+cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=
+cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=
+cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=
+cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=
+cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
+cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=
+cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
+cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
+cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=
+cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=
+cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=
+cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=
+cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=
+cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=
+cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=
+cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY=
+cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=
+cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=
+cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc=
+cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=
+cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8=
+cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=
+cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=
+cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=
+cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=
+cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=
+cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=
+cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=
+cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
+cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
+cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
+cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
+git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
+github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
+github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
+github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
+github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/couchbase/gocb v1.6.7/go.mod h1:AtRhXLpjgHmkRgG3e0K9t41qnWFonb8iohS/u/TZzxM=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
+github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
+github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
+github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
+github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
+github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
+github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=
+github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
+github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
+github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
+github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
+github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
+github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
+github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
+github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
+github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
+github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
+github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
+github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
+github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
+github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
+github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
+github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
+github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
+github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
+github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
+github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
+github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
+github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
+github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
+go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
+go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.27.0/go.mod h1:2T1VrynTXNdK4uB/hHqnvjTYL09wJWDGp8sOcJIMqjA=
+go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
+go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.27.0/go.mod h1:lOgrT5C3ORdbqp2LsDrx+pBj6gbZtQ5Omk27vH3EaW0=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
+golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
+golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
+golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
+gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
+gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
+gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
+google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
+google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
+google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
+google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
+google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
+google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
+google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=
+google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
+google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
+google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/couchbase/gocbcore.v7 v7.1.18/go.mod h1:48d2Be0MxRtsyuvn+mWzqmoGUG9uA00ghopzOs148/E=
+gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4/go.mod h1:ZjII0iKx4Veo6N6da+pEZu/ptNyKLg9QTVt7fFmR6sw=
+gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4/go.mod h1:jl/gd/aQ2S8whKVSTnsPs6n7BPeaAuw9UglBD/OF7eo=
+gopkg.in/couchbaselabs/jsonx.v1 v1.0.1/go.mod h1:oR201IRovxvLW/eISevH12/+MiKHtNQAKfcX8iWZvJY=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
+k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
+k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
+k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
+k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
+k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
+modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
+modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
+modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
+modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
+modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
+modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
+modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
+modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
+modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
+modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
+modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
+modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
+modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
+modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
+modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
+modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
+modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
+modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/_examples/opentelemetry-custom-provider/main.go b/_examples/opentelemetry-custom-provider/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..581c1ce80ceb649d540df59f22fe5ec1bce1a235
--- /dev/null
+++ b/_examples/opentelemetry-custom-provider/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+ "go.opentelemetry.io/otel"
+ stdout "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
+ "go.opentelemetry.io/otel/metric"
+ controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
+ processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
+ "go.opentelemetry.io/otel/sdk/metric/selector/simple"
+)
+
+type (
+ hello struct{ Who string }
+ helloActor struct{}
+)
+
+func (state *helloActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *hello:
+ fmt.Printf("Hello %s\n", msg.Who)
+ }
+}
+
+func main() {
+ ctx := context.Background()
+ provider := stdoutExporter(ctx)
+ defer func() {
+ if err := provider.(*controller.Controller).Stop(ctx); err != nil {
+ log.Fatalf("could not stop push controller: %v", err)
+ }
+ }()
+
+ config := actor.Configure(actor.WithMetricProviders(provider))
+ system := actor.NewActorSystemWithConfig(config)
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &helloActor{}
+ })
+
+ pid := system.Root.Spawn(props)
+ system.Root.Request(pid, &hello{Who: "Stdout Exporter"})
+ time.Sleep(100 * time.Millisecond)
+ _, _ = console.ReadLine()
+}
+
+func stdoutExporter(ctx context.Context) metric.MeterProvider {
+ exporter, _ := stdout.New(stdout.WithPrettyPrint())
+ provider := controller.New(
+ processor.NewFactory(
+ simple.NewWithInexpensiveDistribution(),
+ exporter,
+ ),
+ controller.WithExporter(exporter),
+ )
+
+ if err := provider.Start(ctx); err != nil {
+ log.Fatalf("could not start push controller: %v", err)
+ }
+ otel.SetMeterProvider(provider)
+
+ return provider
+}
diff --git a/_examples/opentelemetry/go.mod b/_examples/opentelemetry/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a8d54f82a998e33a5b01d9878623b9072fa78bff
--- /dev/null
+++ b/_examples/opentelemetry/go.mod
@@ -0,0 +1,39 @@
+module opentelemetry
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/opentelemetry/go.sum b/_examples/opentelemetry/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/opentelemetry/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/opentelemetry/main.go b/_examples/opentelemetry/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..b35909a405b1e5bd6828000b5e60121fb29af91c
--- /dev/null
+++ b/_examples/opentelemetry/main.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ console "github.com/asynkron/goconsole"
+)
+
+type (
+ hello struct{ Who string }
+ helloActor struct{}
+)
+
+func (state *helloActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *hello:
+ fmt.Printf("Hello %s\n", msg.Who)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem(actor.WithDefaultPrometheusProvider(2222))
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &helloActor{}
+ })
+
+ pid := system.Root.Spawn(props)
+ system.Root.Request(pid, &hello{Who: "Prometheus Exporter"})
+ time.Sleep(100 * time.Millisecond)
+ fmt.Println("Visit http://localhost:2222")
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/persistence/go.mod b/_examples/persistence/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..159e8aeabb34c4513acb9f8de1d985828a60239e
--- /dev/null
+++ b/_examples/persistence/go.mod
@@ -0,0 +1,39 @@
+module persistence
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+)
diff --git a/_examples/persistence/go.sum b/_examples/persistence/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/persistence/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/persistence/main.go b/_examples/persistence/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..3dbfe84c010fe49b50689706187409cda2babf15
--- /dev/null
+++ b/_examples/persistence/main.go
@@ -0,0 +1,222 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "strconv"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/persistence"
+ console "github.com/asynkron/goconsole"
+ "google.golang.org/protobuf/encoding/prototext"
+ "google.golang.org/protobuf/reflect/protodesc"
+ "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/runtime/protoiface"
+ "google.golang.org/protobuf/types/descriptorpb"
+)
+
+type Provider struct {
+ providerState persistence.ProviderState
+}
+
+func NewProvider(snapshotInterval int) *Provider {
+ return &Provider{
+ providerState: persistence.NewInMemoryProvider(snapshotInterval),
+ }
+}
+
+func (p *Provider) InitState(actorName string, eventNum, eventIndexAfterSnapshot int) {
+ for i := 0; i < eventNum; i++ {
+ p.providerState.PersistEvent(
+ actorName,
+ i,
+ &Message{protoMsg: protoMsg{state: "state" + strconv.Itoa(i)}},
+ )
+ }
+ p.providerState.PersistSnapshot(
+ actorName,
+ eventIndexAfterSnapshot,
+ &Snapshot{protoMsg: protoMsg{state: "state" + strconv.Itoa(eventIndexAfterSnapshot-1)}},
+ )
+}
+
+func (p *Provider) GetState() persistence.ProviderState {
+ return p.providerState
+}
+
+type protoMsg struct {
+ state string
+ set bool
+ value string
+}
+
+func (p *protoMsg) Reset() {}
+func (p *protoMsg) String() string { return p.state }
+func (p *protoMsg) ProtoMessage() {}
+
+type (
+ Message struct{ protoMsg }
+ Snapshot struct{ protoMsg }
+)
+
+func (m *protoMsg) ProtoReflect() protoreflect.Message { return (*message)(m) }
+
+type message protoMsg
+
+type messageType struct{}
+
+func (messageType) New() protoreflect.Message { return &message{} }
+func (messageType) Zero() protoreflect.Message { return (*message)(nil) }
+func (messageType) Descriptor() protoreflect.MessageDescriptor { return fileDesc.Messages().Get(0) }
+
+func (m *message) New() protoreflect.Message { return &message{} }
+func (m *message) Descriptor() protoreflect.MessageDescriptor { return fileDesc.Messages().Get(0) }
+func (m *message) Type() protoreflect.MessageType { return messageType{} }
+func (m *message) Interface() protoreflect.ProtoMessage { return (*protoMsg)(m) }
+func (m *message) ProtoMethods() *protoiface.Methods { return nil }
+
+var fieldDescS = fileDesc.Messages().Get(0).Fields().Get(0)
+
+func (m *message) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) {
+ if m.set {
+ f(fieldDescS, protoreflect.ValueOf(m.value))
+ }
+}
+
+func (m *message) Has(fd protoreflect.FieldDescriptor) bool {
+ if fd == fieldDescS {
+ return m.set
+ }
+ panic("invalid field descriptor")
+}
+
+func (m *message) Clear(fd protoreflect.FieldDescriptor) {
+ if fd == fieldDescS {
+ m.value = ""
+ m.set = false
+ return
+ }
+ panic("invalid field descriptor")
+}
+
+func (m *message) Get(fd protoreflect.FieldDescriptor) protoreflect.Value {
+ if fd == fieldDescS {
+ return protoreflect.ValueOf(m.value)
+ }
+ panic("invalid field descriptor")
+}
+
+func (m *message) Set(fd protoreflect.FieldDescriptor, v protoreflect.Value) {
+ if fd == fieldDescS {
+ m.value = v.String()
+ m.set = true
+ return
+ }
+ panic("invalid field descriptor")
+}
+
+func (m *message) Mutable(protoreflect.FieldDescriptor) protoreflect.Value {
+ panic("invalid field descriptor")
+}
+
+func (m *message) NewField(protoreflect.FieldDescriptor) protoreflect.Value {
+ panic("invalid field descriptor")
+}
+
+func (m *message) WhichOneof(protoreflect.OneofDescriptor) protoreflect.FieldDescriptor {
+ panic("invalid oneof descriptor")
+}
+
+func (m *message) GetUnknown() protoreflect.RawFields { return nil }
+
+// func (m *message) SetUnknown(protoreflect.RawFields) { return }
+func (m *message) SetUnknown(protoreflect.RawFields) {}
+
+func (m *message) IsValid() bool {
+ return m != nil
+}
+
+var fileDesc = func() protoreflect.FileDescriptor {
+ p := &descriptorpb.FileDescriptorProto{}
+ if err := prototext.Unmarshal([]byte(descriptorText), p); err != nil {
+ panic(err)
+ }
+ file, err := protodesc.NewFile(p, nil)
+ if err != nil {
+ panic(err)
+ }
+ return file
+}()
+
+const descriptorText = `
+ name: "internal/testprotos/irregular/irregular.proto"
+ package: "goproto.proto.thirdparty"
+ message_type {
+ name: "IrregularMessage"
+ field {
+ name: "s"
+ number: 1
+ label: LABEL_OPTIONAL
+ type: TYPE_STRING
+ json_name: "s"
+ }
+ }
+ options {
+ go_package: "google.golang.org/protobuf/internal/testprotos/irregular"
+ }
+`
+
+type AberrantMessage int
+
+func (m AberrantMessage) ProtoMessage() {}
+func (m AberrantMessage) Reset() {}
+func (m AberrantMessage) String() string { return "" }
+func (m AberrantMessage) Marshal() ([]byte, error) { return nil, nil }
+func (m AberrantMessage) Unmarshal([]byte) error { return nil }
+
+type Actor struct {
+ persistence.Mixin
+ state string
+}
+
+func (a *Actor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ log.Println("actor started")
+ case *persistence.RequestSnapshot:
+ log.Printf("snapshot internal state '%v'", a.state)
+ a.PersistSnapshot(&Snapshot{protoMsg: protoMsg{state: a.state}})
+ case *Snapshot:
+ a.state = msg.state
+ log.Printf("recovered from snapshot, internal state changed to '%v'", a.state)
+ case *persistence.ReplayComplete:
+ log.Printf("replay completed, internal state changed to '%v'", a.state)
+ case *Message:
+ scenario := "received replayed event"
+ if !a.Recovering() {
+ a.PersistReceive(msg)
+ scenario = "received new message"
+ }
+ a.state = msg.state
+ log.Printf("%s, internal state changed to '%v'\n", scenario, a.state)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ provider := NewProvider(3)
+ provider.InitState("persistent", 4, 3)
+
+ rootContext := system.Root
+ props := actor.PropsFromProducer(func() actor.Actor { return &Actor{} },
+ actor.WithReceiverMiddleware(persistence.Using(provider)))
+ pid, _ := rootContext.SpawnNamed(props, "persistent")
+ rootContext.Send(pid, &Message{protoMsg: protoMsg{state: "state4"}})
+ rootContext.Send(pid, &Message{protoMsg: protoMsg{state: "state5"}})
+
+ rootContext.PoisonFuture(pid).Wait()
+ fmt.Printf("*** restart ***\n")
+ pid, _ = rootContext.SpawnNamed(props, "persistent")
+
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/remote-activate/Makefile b/_examples/remote-activate/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a4c35f1d4ba7a4575c1c4a92937addde15c2eec2
--- /dev/null
+++ b/_examples/remote-activate/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node2/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node1/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-activate/go.mod b/_examples/remote-activate/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..1998f7f1354eabea81a0793f154701e1d9b12ebf
--- /dev/null
+++ b/_examples/remote-activate/go.mod
@@ -0,0 +1,46 @@
+module remoteactivate
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-activate/go.sum b/_examples/remote-activate/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..af32feca5fbf879242aa12c72a2d4e3f94331558
--- /dev/null
+++ b/_examples/remote-activate/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-activate/messages/build.sh b/_examples/remote-activate/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-activate/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-activate/messages/protos.pb.go b/_examples/remote-activate/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef16843e9b2c4c3bd1cd25208c706f478ad14901
--- /dev/null
+++ b/_examples/remote-activate/messages/protos.pb.go
@@ -0,0 +1,198 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HelloRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *HelloRequest) Reset() {
+ *x = HelloRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type HelloResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *HelloResponse) Reset() {
+ *x = HelloResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResponse) ProtoMessage() {}
+
+func (x *HelloResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
+func (*HelloResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c,
+ 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c,
+ 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+ 0x65, 0x73, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
+ 0x74, 0x65, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_protos_proto_goTypes = []interface{}{
+ (*HelloRequest)(nil), // 0: messages.HelloRequest
+ (*HelloResponse)(nil), // 1: messages.HelloResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-activate/messages/protos.proto b/_examples/remote-activate/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..4c902f7f1c0af5f2d2d957113953a41fb6b79bb8
--- /dev/null
+++ b/_examples/remote-activate/messages/protos.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-activate/messages";
+
+message HelloRequest {}
+message HelloResponse {
+ string Message = 1;
+}
\ No newline at end of file
diff --git a/_examples/remote-activate/node1/main.go b/_examples/remote-activate/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..fecf532996a6233e4fc7b476a14fc9ab94a1e984
--- /dev/null
+++ b/_examples/remote-activate/node1/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "remoteactivate/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ timeout := 5 * time.Second
+
+ system := actor.NewActorSystem()
+ remoteConfig := remote.Configure("127.0.0.1", 8081)
+ r := remote.NewRemote(system, remoteConfig)
+ r.Start()
+
+ rootContext := system.Root
+
+ // props := actor.
+ // PropsFromFunc(func(context actor.Context) {
+ // switch context.Message().(type) {
+ // case *actor.Started:
+ // log.Printf("actor started " + context.Self().String())
+ // case *messages.HelloRequest:
+ // log.Println("Received pong from sender")
+ // message := &messages.HelloResponse{Message: "hello from remote"}
+ // context.Request(context.Sender(), message)
+ // }
+ // })
+
+ pidResp, _ := r.SpawnNamed("127.0.0.1:8080", "world", "hello", timeout)
+
+ res, _ := rootContext.RequestFuture(pidResp.Pid, &messages.HelloRequest{}, timeout).Result()
+
+ response := res.(*messages.HelloResponse)
+
+ fmt.Printf("Response from remote %v, sender=%s", response.Message, pidResp.Pid.String())
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-activate/node2/main.go b/_examples/remote-activate/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a39f4ce1997102e110f2b4897a99abe638c2ab8
--- /dev/null
+++ b/_examples/remote-activate/node2/main.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "runtime"
+
+ "remoteactivate/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+type helloActor struct{}
+
+func (*helloActor) Receive(ctx actor.Context) {
+ switch ctx.Message().(type) {
+ case *messages.HelloRequest:
+ ctx.Respond(&messages.HelloResponse{
+ Message: "Hello from remote node",
+ })
+ }
+}
+
+func newHelloActor() actor.Actor {
+ return &helloActor{}
+}
+
+func main() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+
+ system := actor.NewActorSystem()
+ remoteConfig := remote.Configure("127.0.0.1", 8080,
+ remote.WithKinds(remote.NewKind("hello", actor.PropsFromProducer(newHelloActor))))
+
+ remoter := remote.NewRemote(system, remoteConfig)
+ remoter.Start()
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-advertised-address/Makefile b/_examples/remote-advertised-address/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a4c35f1d4ba7a4575c1c4a92937addde15c2eec2
--- /dev/null
+++ b/_examples/remote-advertised-address/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node2/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node1/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-advertised-address/go.mod b/_examples/remote-advertised-address/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..325a43369f10a0d7511c560b07328ef4893c5be2
--- /dev/null
+++ b/_examples/remote-advertised-address/go.mod
@@ -0,0 +1,46 @@
+module remoteadvertisedaddress
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-advertised-address/go.sum b/_examples/remote-advertised-address/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..a2b3557700cd9f38d7fffd4be0c6b4e677683c36
--- /dev/null
+++ b/_examples/remote-advertised-address/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-advertised-address/messages/build.sh b/_examples/remote-advertised-address/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-advertised-address/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-advertised-address/messages/protos.pb.go b/_examples/remote-advertised-address/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..9854fb27053cdea9434744975c024ff76e99c66b
--- /dev/null
+++ b/_examples/remote-advertised-address/messages/protos.pb.go
@@ -0,0 +1,305 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Start struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Start) Reset() {
+ *x = Start{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Start) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Start) ProtoMessage() {}
+
+func (x *Start) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Start.ProtoReflect.Descriptor instead.
+func (*Start) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type StartRemote struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Sender *actor.PID `protobuf:"bytes,1,opt,name=Sender,proto3" json:"Sender,omitempty"`
+}
+
+func (x *StartRemote) Reset() {
+ *x = StartRemote{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *StartRemote) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StartRemote) ProtoMessage() {}
+
+func (x *StartRemote) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use StartRemote.ProtoReflect.Descriptor instead.
+func (*StartRemote) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *StartRemote) GetSender() *actor.PID {
+ if x != nil {
+ return x.Sender
+ }
+ return nil
+}
+
+type Ping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Ping) Reset() {
+ *x = Ping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Ping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ping) ProtoMessage() {}
+
+func (x *Ping) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
+func (*Ping) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+type Pong struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Pong) Reset() {
+ *x = Pong{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Pong) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Pong) ProtoMessage() {}
+
+func (x *Pong) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
+func (*Pong) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, 0x31,
+ 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x0a,
+ 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65,
+ 0x72, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e,
+ 0x67, 0x42, 0x50, 0x5a, 0x4e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73,
+ 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73,
+ 0x65, 0x64, 0x2d, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61,
+ 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_protos_proto_goTypes = []interface{}{
+ (*Start)(nil), // 0: messages.Start
+ (*StartRemote)(nil), // 1: messages.StartRemote
+ (*Ping)(nil), // 2: messages.Ping
+ (*Pong)(nil), // 3: messages.Pong
+ (*actor.PID)(nil), // 4: actor.PID
+}
+var file_protos_proto_depIdxs = []int32{
+ 4, // 0: messages.StartRemote.Sender:type_name -> actor.PID
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Start); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*StartRemote); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Ping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Pong); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 4,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-advertised-address/messages/protos.proto b/_examples/remote-advertised-address/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..2097f6b76c4957bd5dcc51179ed418ed3e444b8d
--- /dev/null
+++ b/_examples/remote-advertised-address/messages/protos.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-advertised-address/messages";
+import "actor.proto";
+
+message Start {}
+message StartRemote {
+ actor.PID Sender = 1;
+}
+message Ping {}
+message Pong {}
diff --git a/_examples/remote-advertised-address/node1/main.go b/_examples/remote-advertised-address/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..bb26829216d1e73838b8a6f510a3848dc7a838c3
--- /dev/null
+++ b/_examples/remote-advertised-address/node1/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "log"
+
+ "remoteadvertisedaddress/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ rootContext = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("0.0.0.0", 8081, remote.WithAdvertisedHost("localhost:8081"))
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ remotePid := actor.NewPID("127.0.0.1:8080", "remote")
+
+ props := actor.
+ PropsFromFunc(func(context actor.Context) {
+ switch context.Message().(type) {
+ case *actor.Started:
+ message := &messages.Ping{}
+ context.Request(remotePid, message)
+
+ case *messages.Pong:
+ log.Println("Received pong from sender")
+ }
+ })
+
+ rootContext.Spawn(props)
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-advertised-address/node2/main.go b/_examples/remote-advertised-address/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..21850e00574a35a0cf0510b2a7a2abd8f7e7d2eb
--- /dev/null
+++ b/_examples/remote-advertised-address/node2/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "fmt"
+
+ "remoteadvertisedaddress/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ rootContext = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("0.0.0.0", 8080, remote.WithAdvertisedHost("localhost:8080"))
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ props := actor.
+ PropsFromFunc(
+ func(context actor.Context) {
+ switch context.Message().(type) {
+ case *messages.Ping:
+ fmt.Println("Received ping from sender with address: " + context.Sender().Address)
+ context.Respond(&messages.Pong{})
+ }
+ })
+
+ rootContext.SpawnNamed(props, "remote")
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-benchmark/Makefile b/_examples/remote-benchmark/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a4c35f1d4ba7a4575c1c4a92937addde15c2eec2
--- /dev/null
+++ b/_examples/remote-benchmark/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node2/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node1/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-benchmark/go.mod b/_examples/remote-benchmark/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..2e4dc1b1619a55126b630e0c9cc55bce3ffa9f2e
--- /dev/null
+++ b/_examples/remote-benchmark/go.mod
@@ -0,0 +1,46 @@
+module remotebenchmark
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-benchmark/go.sum b/_examples/remote-benchmark/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..a2b3557700cd9f38d7fffd4be0c6b4e677683c36
--- /dev/null
+++ b/_examples/remote-benchmark/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-benchmark/messages/build.sh b/_examples/remote-benchmark/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-benchmark/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-benchmark/messages/protos.pb.go b/_examples/remote-benchmark/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..96f87c0d7a29fae9e40cf7bde4401f98521e9353
--- /dev/null
+++ b/_examples/remote-benchmark/messages/protos.pb.go
@@ -0,0 +1,305 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Start struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Start) Reset() {
+ *x = Start{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Start) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Start) ProtoMessage() {}
+
+func (x *Start) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Start.ProtoReflect.Descriptor instead.
+func (*Start) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type StartRemote struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Sender *actor.PID `protobuf:"bytes,1,opt,name=Sender,proto3" json:"Sender,omitempty"`
+}
+
+func (x *StartRemote) Reset() {
+ *x = StartRemote{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *StartRemote) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StartRemote) ProtoMessage() {}
+
+func (x *StartRemote) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use StartRemote.ProtoReflect.Descriptor instead.
+func (*StartRemote) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *StartRemote) GetSender() *actor.PID {
+ if x != nil {
+ return x.Sender
+ }
+ return nil
+}
+
+type Ping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Ping) Reset() {
+ *x = Ping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Ping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ping) ProtoMessage() {}
+
+func (x *Ping) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
+func (*Ping) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+type Pong struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Pong) Reset() {
+ *x = Pong{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Pong) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Pong) ProtoMessage() {}
+
+func (x *Pong) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
+func (*Pong) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, 0x31,
+ 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x0a,
+ 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65,
+ 0x72, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e,
+ 0x67, 0x42, 0x47, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73,
+ 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x62, 0x65, 0x6e, 0x63, 0x68, 0x6d, 0x61, 0x72,
+ 0x6b, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_protos_proto_goTypes = []interface{}{
+ (*Start)(nil), // 0: messages.Start
+ (*StartRemote)(nil), // 1: messages.StartRemote
+ (*Ping)(nil), // 2: messages.Ping
+ (*Pong)(nil), // 3: messages.Pong
+ (*actor.PID)(nil), // 4: actor.PID
+}
+var file_protos_proto_depIdxs = []int32{
+ 4, // 0: messages.StartRemote.Sender:type_name -> actor.PID
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Start); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*StartRemote); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Ping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Pong); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 4,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-benchmark/messages/protos.proto b/_examples/remote-benchmark/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..1328b5c58579e30be74ed7b4eb0a56aba9cfc7d4
--- /dev/null
+++ b/_examples/remote-benchmark/messages/protos.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-benchmark/messages";
+import "actor.proto";
+
+message Start {}
+message StartRemote {
+ actor.PID Sender = 1;
+}
+message Ping {}
+message Pong {}
diff --git a/_examples/remote-benchmark/node1/main.go b/_examples/remote-benchmark/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..cabe0c45405cb1fce025de84b750cf9ad7052cfa
--- /dev/null
+++ b/_examples/remote-benchmark/node1/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "sync"
+ "time"
+
+ "remotebenchmark/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+type localActor struct {
+ count int
+ wgStop *sync.WaitGroup
+ messageCount int
+}
+
+func (state *localActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ case *messages.Pong:
+ state.count++
+ //if state.count%50000 == 0 {
+ // log.Println(state.count)
+ //}
+ if state.count == state.messageCount {
+ state.wgStop.Done()
+ }
+ }
+}
+
+func newLocalActor(stop *sync.WaitGroup, messageCount int) actor.Producer {
+ return func() actor.Actor {
+ return &localActor{
+ wgStop: stop,
+ messageCount: messageCount,
+ }
+ }
+}
+
+var (
+ cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
+ blockProfile = flag.String("blockprof", "", "execute contention profiling and save results here")
+)
+
+func main() {
+ flag.Parse()
+ if *cpuprofile != "" {
+ f, err := os.Create(*cpuprofile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+ }
+
+ // Check for lock contention profiling
+ if *blockProfile != "" {
+ prof, err := os.Create(*blockProfile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ runtime.SetBlockProfileRate(1)
+ defer func() {
+ pprof.Lookup("block").WriteTo(prof, 0)
+ }()
+ }
+
+ // runtime.GOMAXPROCS(runtime.NumCPU() * 1)
+ // runtime.GC()
+
+ messageCount := 1000000
+ // remote.DefaultSerializerID = 1
+ system := actor.NewActorSystem()
+ r := remote.NewRemote(system, remote.Configure("127.0.0.1", 0 /*, remote.WithCallOptions(grpc.UseCompressor(gzip.Name))*/))
+ r.Start()
+
+ rootContext := system.Root
+
+ run := true
+ go func() {
+ for run == true {
+ var wg sync.WaitGroup
+ props := actor.
+ PropsFromProducer(newLocalActor(&wg, messageCount),
+ actor.WithMailbox(actor.Bounded(1000000)))
+
+ pid := rootContext.Spawn(props)
+
+ pidResponse, err := r.Spawn("127.0.0.1:12000", "echo", time.Second*2000)
+ if err != nil || pidResponse.StatusCode != 0 {
+ rootContext.Stop(pid)
+ return
+ }
+ remotePid := pidResponse.Pid
+ msg := messages.StartRemote{Sender: pid}
+ rootContext.RequestFuture(remotePid, &msg, 5*time.Second).Wait()
+ wg.Add(1)
+
+ start := time.Now()
+ log.Println("Starting to send")
+
+ message := &messages.Ping{}
+ for i := 0; i < messageCount; i++ {
+ rootContext.Send(remotePid, message)
+ }
+
+ wg.Wait()
+ elapsed := time.Since(start)
+ log.Printf("Elapsed %s", elapsed)
+
+ x := int(float32(messageCount*2) / (float32(elapsed) / float32(time.Second)))
+ log.Printf("Msg per sec %v", x)
+ rootContext.Stop(remotePid)
+ rootContext.Stop(pid)
+ }
+ }()
+ console.ReadLine()
+ run = false
+ console.ReadLine()
+ r.Shutdown(true)
+}
diff --git a/_examples/remote-benchmark/node2/main.go b/_examples/remote-benchmark/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7e88e6029c98a932e6f64fece2f6711bdfb2bce0
--- /dev/null
+++ b/_examples/remote-benchmark/node2/main.go
@@ -0,0 +1,47 @@
+package main
+
+import (
+ "log"
+
+ "remotebenchmark/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+type echoActor struct {
+ sender *actor.PID
+}
+
+func (state *echoActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *messages.StartRemote:
+ log.Printf("Starting for %s", msg.Sender)
+ state.sender = msg.Sender
+ context.Respond(&messages.Start{})
+ case *messages.Ping:
+ context.Send(state.sender, &messages.Pong{})
+ }
+}
+
+func main() {
+ // runtime.GOMAXPROCS(runtime.NumCPU() * 1)
+ // runtime.GC()
+
+ props := actor.
+ PropsFromProducer(func() actor.Actor { return &echoActor{} },
+ actor.WithMailbox(actor.Bounded(1000000)))
+
+ system := actor.NewActorSystem()
+ r := remote.NewRemote(system, remote.Configure("127.0.0.1", 12000 /*, remote.WithCallOptions(grpc.UseCompressor(gzip.Name))*/))
+ r.Register("echo", props)
+ r.Start()
+
+ rootContext := system.Root
+
+ rootContext.SpawnNamed(props, "remote")
+
+ console.ReadLine()
+ r.Shutdown(true)
+}
diff --git a/_examples/remote-channels/Makefile b/_examples/remote-channels/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..0bb231412a50360c6f984c3a358922c50ce05e49
--- /dev/null
+++ b/_examples/remote-channels/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node1/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node2/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-channels/go.mod b/_examples/remote-channels/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a27b3faf382c8cac46c1bea306024fd871b33bd2
--- /dev/null
+++ b/_examples/remote-channels/go.mod
@@ -0,0 +1,46 @@
+module distributedchannels
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-channels/go.sum b/_examples/remote-channels/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..af32feca5fbf879242aa12c72a2d4e3f94331558
--- /dev/null
+++ b/_examples/remote-channels/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-channels/messages/build.sh b/_examples/remote-channels/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-channels/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-channels/messages/protos.pb.go b/_examples/remote-channels/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..fafe722d8253d28d7af645b263499d7d429a365e
--- /dev/null
+++ b/_examples/remote-channels/messages/protos.pb.go
@@ -0,0 +1,145 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type MyMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *MyMessage) Reset() {
+ *x = MyMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MyMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MyMessage) ProtoMessage() {}
+
+func (x *MyMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MyMessage.ProtoReflect.Descriptor instead.
+func (*MyMessage) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MyMessage) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x25, 0x0a, 0x09, 0x4d, 0x79, 0x4d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42,
+ 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73,
+ 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f,
+ 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x72,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x2f, 0x6d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_protos_proto_goTypes = []interface{}{
+ (*MyMessage)(nil), // 0: messages.MyMessage
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MyMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-channels/messages/protos.proto b/_examples/remote-channels/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..7166c254cdccabcca351d9be841fde957bb3fdd6
--- /dev/null
+++ b/_examples/remote-channels/messages/protos.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-channels/messages";
+
+message MyMessage {
+ string Message = 1;
+}
\ No newline at end of file
diff --git a/_examples/remote-channels/node1/main.go b/_examples/remote-channels/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b0cb86172673eb04dc0890937b37801f8cfa78d
--- /dev/null
+++ b/_examples/remote-channels/node1/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+
+ "distributedchannels/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func newMyMessageSenderChannel(context actor.SenderContext) chan<- *messages.MyMessage {
+ channel := make(chan *messages.MyMessage)
+ remoteChannel := actor.NewPID("127.0.0.1:8080", "MyMessage")
+ go func() {
+ for msg := range channel {
+ context.Send(remoteChannel, msg)
+ }
+ }()
+
+ return channel
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ remoteConfig := remote.Configure("127.0.0.1", 0)
+ remoting := remote.NewRemote(system, remoteConfig)
+ remoting.Start()
+
+ channel := newMyMessageSenderChannel(system.Root)
+
+ for i := 0; i < 10; i++ {
+ message := &messages.MyMessage{
+ Message: fmt.Sprintf("hello %v", i),
+ }
+ channel <- message
+ }
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-channels/node2/main.go b/_examples/remote-channels/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5fad1cc7fbabb402d2d0eff3984a0bb1106e24b
--- /dev/null
+++ b/_examples/remote-channels/node2/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "log"
+
+ "distributedchannels/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ system := actor.NewActorSystem()
+ remoteConfig := remote.Configure("127.0.0.1", 8080)
+ remoting := remote.NewRemote(system, remoteConfig)
+ remoting.Start()
+
+ // create the channel
+ channel := make(chan *messages.MyMessage)
+
+ // create an actor receiving messages and pushing them onto the channel
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ if msg, ok := context.Message().(*messages.MyMessage); ok {
+ channel <- msg
+ }
+ })
+
+ // spawn
+ _, _ = system.Root.SpawnNamed(props, "MyMessage")
+
+ // consume the channel just like you use to
+ go func() {
+ for msg := range channel {
+ log.Println(msg)
+ }
+ }()
+
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/remote-chat/Makefile b/_examples/remote-chat/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..6def3182bcb1823c11c952164db4d6184e130e40
--- /dev/null
+++ b/_examples/remote-chat/Makefile
@@ -0,0 +1,13 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux split-window -t "eg:0.1" -h
+ tmux send-keys -t "eg:0.0" "go run server/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run client/main.go" Enter
+ tmux send-keys -t "eg:0.2" "go run client/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-chat/client/main.go b/_examples/remote-chat/client/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..e14f8ba4cf5b0da166379f2a8f437de47e2051e6
--- /dev/null
+++ b/_examples/remote-chat/client/main.go
@@ -0,0 +1,58 @@
+package main
+
+import (
+ "log"
+
+ "chat/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+func main() {
+ system := actor.NewActorSystem()
+ config := remote.Configure("127.0.0.1", 0)
+ remoter := remote.NewRemote(system, config)
+ remoter.Start()
+
+ server := actor.NewPID("127.0.0.1:8080", "chatserver")
+
+ // define root context
+ rootContext := system.Root
+
+ // spawn our chat client inline
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *messages.Connected:
+ log.Println(msg.Message)
+ case *messages.SayResponse:
+ log.Printf("%v: %v", msg.UserName, msg.Message)
+ case *messages.NickResponse:
+ log.Printf("%v is now known as %v", msg.OldUserName, msg.NewUserName)
+ }
+ })
+
+ client := rootContext.Spawn(props)
+
+ rootContext.Send(server, &messages.Connect{
+ Sender: client,
+ })
+
+ nick := "Roger"
+ cons := console.NewConsole(func(text string) {
+ rootContext.Send(server, &messages.SayRequest{
+ UserName: nick,
+ Message: text,
+ })
+ })
+ // write /nick NAME to change your chat username
+ cons.Command("/nick", func(newNick string) {
+ rootContext.Send(server, &messages.NickRequest{
+ OldUserName: nick,
+ NewUserName: newNick,
+ })
+ nick = newNick
+ })
+ cons.Run()
+}
diff --git a/_examples/remote-chat/go.mod b/_examples/remote-chat/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..760757e4a15906933c9b86cb99f6e603d3eeccc6
--- /dev/null
+++ b/_examples/remote-chat/go.mod
@@ -0,0 +1,46 @@
+module chat
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-chat/go.sum b/_examples/remote-chat/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..af32feca5fbf879242aa12c72a2d4e3f94331558
--- /dev/null
+++ b/_examples/remote-chat/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-chat/messages/build.sh b/_examples/remote-chat/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-chat/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-chat/messages/protos.pb.go b/_examples/remote-chat/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..616c76ff0c669a3ab003c68940ddcb083e0cb30e
--- /dev/null
+++ b/_examples/remote-chat/messages/protos.pb.go
@@ -0,0 +1,503 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Connect struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Sender *actor.PID `protobuf:"bytes,1,opt,name=Sender,proto3" json:"Sender,omitempty"`
+}
+
+func (x *Connect) Reset() {
+ *x = Connect{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Connect) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Connect) ProtoMessage() {}
+
+func (x *Connect) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Connect.ProtoReflect.Descriptor instead.
+func (*Connect) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Connect) GetSender() *actor.PID {
+ if x != nil {
+ return x.Sender
+ }
+ return nil
+}
+
+type Connected struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *Connected) Reset() {
+ *x = Connected{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Connected) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Connected) ProtoMessage() {}
+
+func (x *Connected) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Connected.ProtoReflect.Descriptor instead.
+func (*Connected) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Connected) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type SayRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ UserName string `protobuf:"bytes,1,opt,name=UserName,proto3" json:"UserName,omitempty"`
+ Message string `protobuf:"bytes,2,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *SayRequest) Reset() {
+ *x = SayRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SayRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SayRequest) ProtoMessage() {}
+
+func (x *SayRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SayRequest.ProtoReflect.Descriptor instead.
+func (*SayRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *SayRequest) GetUserName() string {
+ if x != nil {
+ return x.UserName
+ }
+ return ""
+}
+
+func (x *SayRequest) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type SayResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ UserName string `protobuf:"bytes,1,opt,name=UserName,proto3" json:"UserName,omitempty"`
+ Message string `protobuf:"bytes,2,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *SayResponse) Reset() {
+ *x = SayResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SayResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SayResponse) ProtoMessage() {}
+
+func (x *SayResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SayResponse.ProtoReflect.Descriptor instead.
+func (*SayResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *SayResponse) GetUserName() string {
+ if x != nil {
+ return x.UserName
+ }
+ return ""
+}
+
+func (x *SayResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+type NickRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ OldUserName string `protobuf:"bytes,1,opt,name=OldUserName,proto3" json:"OldUserName,omitempty"`
+ NewUserName string `protobuf:"bytes,2,opt,name=NewUserName,proto3" json:"NewUserName,omitempty"`
+}
+
+func (x *NickRequest) Reset() {
+ *x = NickRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NickRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NickRequest) ProtoMessage() {}
+
+func (x *NickRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NickRequest.ProtoReflect.Descriptor instead.
+func (*NickRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *NickRequest) GetOldUserName() string {
+ if x != nil {
+ return x.OldUserName
+ }
+ return ""
+}
+
+func (x *NickRequest) GetNewUserName() string {
+ if x != nil {
+ return x.NewUserName
+ }
+ return ""
+}
+
+type NickResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ OldUserName string `protobuf:"bytes,1,opt,name=OldUserName,proto3" json:"OldUserName,omitempty"`
+ NewUserName string `protobuf:"bytes,2,opt,name=NewUserName,proto3" json:"NewUserName,omitempty"`
+}
+
+func (x *NickResponse) Reset() {
+ *x = NickResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NickResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NickResponse) ProtoMessage() {}
+
+func (x *NickResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NickResponse.ProtoReflect.Descriptor instead.
+func (*NickResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *NickResponse) GetOldUserName() string {
+ if x != nil {
+ return x.OldUserName
+ }
+ return ""
+}
+
+func (x *NickResponse) GetNewUserName() string {
+ if x != nil {
+ return x.NewUserName
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
+ 0x12, 0x22, 0x0a, 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x06, 0x53, 0x65,
+ 0x6e, 0x64, 0x65, 0x72, 0x22, 0x25, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65,
+ 0x64, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x42, 0x0a, 0x0a, 0x53,
+ 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x55, 0x73, 0x65,
+ 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x55, 0x73, 0x65,
+ 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
+ 0x43, 0x0a, 0x0b, 0x53, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a,
+ 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x0b, 0x4e, 0x69, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x6c, 0x64, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61,
+ 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x6c, 0x64, 0x55, 0x73, 0x65,
+ 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72,
+ 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4e, 0x65, 0x77, 0x55,
+ 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x52, 0x0a, 0x0c, 0x4e, 0x69, 0x63, 0x6b, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x4f, 0x6c, 0x64, 0x55, 0x73,
+ 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x4f, 0x6c,
+ 0x64, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x4e, 0x65, 0x77,
+ 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
+ 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x42, 0x5a, 0x40, 0x67,
+ 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72,
+ 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f,
+ 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74,
+ 0x65, 0x2d, 0x63, 0x68, 0x61, 0x74, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62,
+ 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_protos_proto_goTypes = []interface{}{
+ (*Connect)(nil), // 0: messages.Connect
+ (*Connected)(nil), // 1: messages.Connected
+ (*SayRequest)(nil), // 2: messages.SayRequest
+ (*SayResponse)(nil), // 3: messages.SayResponse
+ (*NickRequest)(nil), // 4: messages.NickRequest
+ (*NickResponse)(nil), // 5: messages.NickResponse
+ (*actor.PID)(nil), // 6: actor.PID
+}
+var file_protos_proto_depIdxs = []int32{
+ 6, // 0: messages.Connect.Sender:type_name -> actor.PID
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Connect); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Connected); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SayRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SayResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NickRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NickResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 6,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-chat/messages/protos.proto b/_examples/remote-chat/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..a50f133696ad958d6502630343c8e62b2771b411
--- /dev/null
+++ b/_examples/remote-chat/messages/protos.proto
@@ -0,0 +1,32 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-chat/messages";
+import "actor.proto";
+
+message Connect {
+ actor.PID Sender = 1;
+}
+
+message Connected {
+ string Message = 1;
+}
+
+message SayRequest {
+ string UserName = 1;
+ string Message = 2;
+}
+
+message SayResponse {
+ string UserName = 1;
+ string Message = 2;
+}
+
+message NickRequest {
+ string OldUserName = 1;
+ string NewUserName = 2;
+}
+
+message NickResponse {
+ string OldUserName = 1;
+ string NewUserName = 2;
+}
\ No newline at end of file
diff --git a/_examples/remote-chat/server/main.go b/_examples/remote-chat/server/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4d58ad4a0fc805860d6021c9083ab64e4d963bc7
--- /dev/null
+++ b/_examples/remote-chat/server/main.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "log"
+
+ "chat/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+// define root context
+
+func notifyAll(context actor.Context, clients *actor.PIDSet, message interface{}) {
+ for _, client := range clients.Values() {
+ context.Send(client, message)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ config := remote.Configure("127.0.0.1", 8080)
+ remoter := remote.NewRemote(system, config)
+ remoter.Start()
+
+ clients := actor.NewPIDSet()
+
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *messages.Connect:
+ log.Printf("Client %v connected", msg.Sender)
+ clients.Add(msg.Sender)
+ context.Send(msg.Sender, &messages.Connected{Message: "Welcome!"})
+ case *messages.SayRequest:
+ notifyAll(context, clients, &messages.SayResponse{
+ UserName: msg.UserName,
+ Message: msg.Message,
+ })
+ case *messages.NickRequest:
+ notifyAll(context, clients, &messages.NickResponse{
+ OldUserName: msg.OldUserName,
+ NewUserName: msg.NewUserName,
+ })
+ }
+ })
+
+ _, _ = system.Root.SpawnNamed(props, "chatserver")
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/remote-header/Makefile b/_examples/remote-header/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a4c35f1d4ba7a4575c1c4a92937addde15c2eec2
--- /dev/null
+++ b/_examples/remote-header/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node2/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node1/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-header/go.mod b/_examples/remote-header/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..dc60a6291b08ca2257cced617849e4f7910123a6
--- /dev/null
+++ b/_examples/remote-header/go.mod
@@ -0,0 +1,46 @@
+module remoteheader
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-header/go.sum b/_examples/remote-header/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..af32feca5fbf879242aa12c72a2d4e3f94331558
--- /dev/null
+++ b/_examples/remote-header/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-header/messages/build.sh b/_examples/remote-header/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-header/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-header/messages/protos.pb.go b/_examples/remote-header/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..63b7187c881958615f0d9bddf162b02d50d407ed
--- /dev/null
+++ b/_examples/remote-header/messages/protos.pb.go
@@ -0,0 +1,304 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Start struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Start) Reset() {
+ *x = Start{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Start) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Start) ProtoMessage() {}
+
+func (x *Start) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Start.ProtoReflect.Descriptor instead.
+func (*Start) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type StartRemote struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Sender *actor.PID `protobuf:"bytes,1,opt,name=Sender,proto3" json:"Sender,omitempty"`
+}
+
+func (x *StartRemote) Reset() {
+ *x = StartRemote{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *StartRemote) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StartRemote) ProtoMessage() {}
+
+func (x *StartRemote) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use StartRemote.ProtoReflect.Descriptor instead.
+func (*StartRemote) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *StartRemote) GetSender() *actor.PID {
+ if x != nil {
+ return x.Sender
+ }
+ return nil
+}
+
+type Ping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Ping) Reset() {
+ *x = Ping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Ping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ping) ProtoMessage() {}
+
+func (x *Ping) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
+func (*Ping) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{2}
+}
+
+type Pong struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Pong) Reset() {
+ *x = Pong{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Pong) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Pong) ProtoMessage() {}
+
+func (x *Pong) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
+func (*Pong) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{3}
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, 0x31,
+ 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x12, 0x22, 0x0a,
+ 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x06, 0x53, 0x65, 0x6e, 0x64, 0x65,
+ 0x72, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e,
+ 0x67, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73,
+ 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x6d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_protos_proto_goTypes = []interface{}{
+ (*Start)(nil), // 0: messages.Start
+ (*StartRemote)(nil), // 1: messages.StartRemote
+ (*Ping)(nil), // 2: messages.Ping
+ (*Pong)(nil), // 3: messages.Pong
+ (*actor.PID)(nil), // 4: actor.PID
+}
+var file_protos_proto_depIdxs = []int32{
+ 4, // 0: messages.StartRemote.Sender:type_name -> actor.PID
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Start); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*StartRemote); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Ping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Pong); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 4,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-header/messages/protos.proto b/_examples/remote-header/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..dff3b83f58bba2d5981cd9fb01e8b9b0807e6c5f
--- /dev/null
+++ b/_examples/remote-header/messages/protos.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-header/messages";
+import "actor.proto";
+
+message Start {}
+message StartRemote {
+ actor.PID Sender = 1;
+}
+message Ping {}
+message Pong {}
diff --git a/_examples/remote-header/node1/main.go b/_examples/remote-header/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d62a1ff306f5c8bfccacf81bfe3e2e245495368e
--- /dev/null
+++ b/_examples/remote-header/node1/main.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "log"
+ "time"
+
+ "remoteheader/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ rootContext = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("127.0.0.1", 8081)
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ props := actor.
+ PropsFromFunc(func(context actor.Context) {
+ switch context.Message().(type) {
+ case *messages.Pong:
+ v := context.MessageHeader().Get("test_header")
+ log.Println("Receive pong message with header:" + v)
+ }
+ })
+
+ pid := rootContext.Spawn(props)
+
+ remotePid := actor.NewPID("127.0.0.1:8080", "remote")
+ rootContext.RequestFuture(remotePid, &messages.StartRemote{
+ Sender: pid,
+ }, 5*time.Second).
+ Wait()
+
+ message := &messages.Ping{}
+ rootContext.Send(remotePid, message)
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-header/node2/main.go b/_examples/remote-header/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..0732b0d5eea0423a73812d8281e4411ff40e7813
--- /dev/null
+++ b/_examples/remote-header/node2/main.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "log"
+
+ "remoteheader/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ rootContext = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("127.0.0.1", 8080)
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ var sender *actor.PID
+ props := actor.
+ PropsFromFunc(
+ func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *messages.StartRemote:
+ log.Println("Starting")
+ sender = msg.Sender
+ context.Respond(&messages.Start{})
+ case *messages.Ping:
+ context.Send(sender, &messages.Pong{})
+ }
+ },
+ actor.WithSenderMiddleware(
+ func(next actor.SenderFunc) actor.SenderFunc {
+ return func(ctx actor.SenderContext, target *actor.PID, envelope *actor.MessageEnvelope) {
+ envelope.SetHeader("test_header", "header_from_node2")
+ log.Println("set header")
+ next(ctx, target, envelope)
+ }
+ }))
+
+ rootContext.SpawnNamed(props, "remote")
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-routing/Makefile b/_examples/remote-routing/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..d87df430e0495a25b30b3f700eb38b13ab3caeb0
--- /dev/null
+++ b/_examples/remote-routing/Makefile
@@ -0,0 +1,13 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux split-window -t "eg:0.0" -h
+ tmux send-keys -t "eg:0.0" "go run server/main.go --name node-1 --bind=127.0.0.1:8101" Enter
+ tmux send-keys -t "eg:0.1" "go run server/main.go --name node-2 --bind=127.0.0.1:8102" Enter
+ tmux send-keys -t "eg:0.2" "go run client/*.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-routing/README.md b/_examples/remote-routing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c3988d4ab2d720fe1ead7ffad97c0188b385e1dc
--- /dev/null
+++ b/_examples/remote-routing/README.md
@@ -0,0 +1,12 @@
+# start servers
+
+```bash
+go run server/main.go --name node-1 --bind=127.0.0.1:8101
+go run server/main.go --name node-2 --bind=127.0.0.1:8102
+```
+
+# start client
+
+```bash
+go run client/local.go client/main.go
+```
diff --git a/_examples/remote-routing/client/local.go b/_examples/remote-routing/client/local.go
new file mode 100644
index 0000000000000000000000000000000000000000..d02fb44ca1abd74eb16332e1a4d7aa525b2c7ca5
--- /dev/null
+++ b/_examples/remote-routing/client/local.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "log"
+ "sync"
+
+ "remoterouting/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type localActor struct {
+ count int
+ wgStop *sync.WaitGroup
+ messageCount int
+}
+
+func (state *localActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ case *messages.Pong:
+ state.count++
+ if state.count%50000 == 0 {
+ log.Println(state.count)
+ }
+ if state.count == state.messageCount {
+ log.Println("Done")
+ state.wgStop.Done()
+ }
+ }
+}
+
+func newLocalActor(stop *sync.WaitGroup, messageCount int) actor.Producer {
+ stop.Add(1)
+ return func() actor.Actor {
+ return &localActor{
+ wgStop: stop,
+ messageCount: messageCount,
+ }
+ }
+}
diff --git a/_examples/remote-routing/client/main.go b/_examples/remote-routing/client/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..8c90bf955e7a198aee52baae85320e942dc8c2ae
--- /dev/null
+++ b/_examples/remote-routing/client/main.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "runtime"
+ "sync"
+ "time"
+
+ "remoterouting/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "gitee.com/simplexyz/simpleactor-go/router"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ rootContext = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("127.0.0.1", 8100)
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ runtime.GOMAXPROCS(runtime.NumCPU())
+ runtime.GC()
+
+ p1 := actor.NewPID("127.0.0.1:8101", "remote")
+ p2 := actor.NewPID("127.0.0.1:8102", "remote")
+
+ remotePID := rootContext.Spawn(router.NewConsistentHashGroup(p1, p2))
+
+ messageCount := 1000000
+
+ var wgStop sync.WaitGroup
+
+ props := actor.
+ PropsFromProducer(newLocalActor(&wgStop, messageCount),
+ actor.WithMailbox(actor.Bounded(10000)))
+
+ pid := rootContext.Spawn(props)
+
+ log.Println("Starting to send")
+
+ t := time.Now()
+
+ for i := 0; i < messageCount; i++ {
+ message := &messages.Ping{User: fmt.Sprintf("User_%d", i)}
+ rootContext.RequestWithCustomSender(remotePID, message, pid)
+ }
+
+ wgStop.Wait()
+
+ rootContext.Stop(pid)
+
+ fmt.Printf("elapsed: %v\n", time.Since(t))
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-routing/go.mod b/_examples/remote-routing/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..74a9bf63b236f055957b1e5fe7a6b6ece3243c11
--- /dev/null
+++ b/_examples/remote-routing/go.mod
@@ -0,0 +1,47 @@
+module remoterouting
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-routing/go.sum b/_examples/remote-routing/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..cee93d2919cdc2df54d2dfecea9d5f4b3150a1ec
--- /dev/null
+++ b/_examples/remote-routing/go.sum
@@ -0,0 +1,127 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-routing/messages/build.sh b/_examples/remote-routing/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..8539de66481ea39cc4f5694f78feccec3f440e03
--- /dev/null
+++ b/_examples/remote-routing/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. messages.proto
+
diff --git a/_examples/remote-routing/messages/messages.go b/_examples/remote-routing/messages/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..a14ff310f391a26a56fbe06e5415e886ef198611
--- /dev/null
+++ b/_examples/remote-routing/messages/messages.go
@@ -0,0 +1,5 @@
+package messages
+
+func (m *Ping) Hash() string {
+ return m.User
+}
diff --git a/_examples/remote-routing/messages/messages.pb.go b/_examples/remote-routing/messages/messages.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..a367633cf09bcdb7d2fe71ba3f7d95da0ea6dfe5
--- /dev/null
+++ b/_examples/remote-routing/messages/messages.pb.go
@@ -0,0 +1,196 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: messages.proto
+
+package messages
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Ping struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ User string `protobuf:"bytes,1,opt,name=User,proto3" json:"User,omitempty"`
+}
+
+func (x *Ping) Reset() {
+ *x = Ping{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_messages_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Ping) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Ping) ProtoMessage() {}
+
+func (x *Ping) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Ping.ProtoReflect.Descriptor instead.
+func (*Ping) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Ping) GetUser() string {
+ if x != nil {
+ return x.User
+ }
+ return ""
+}
+
+type Pong struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Pong) Reset() {
+ *x = Pong{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_messages_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Pong) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Pong) ProtoMessage() {}
+
+func (x *Pong) ProtoReflect() protoreflect.Message {
+ mi := &file_messages_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Pong.ProtoReflect.Descriptor instead.
+func (*Pong) Descriptor() ([]byte, []int) {
+ return file_messages_proto_rawDescGZIP(), []int{1}
+}
+
+var File_messages_proto protoreflect.FileDescriptor
+
+var file_messages_proto_rawDesc = []byte{
+ 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x12, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x1a, 0x0a, 0x04, 0x50, 0x69,
+ 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+ 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x22, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x42, 0x45,
+ 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79,
+ 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72,
+ 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x72, 0x65,
+ 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x6d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_messages_proto_rawDescOnce sync.Once
+ file_messages_proto_rawDescData = file_messages_proto_rawDesc
+)
+
+func file_messages_proto_rawDescGZIP() []byte {
+ file_messages_proto_rawDescOnce.Do(func() {
+ file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData)
+ })
+ return file_messages_proto_rawDescData
+}
+
+var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_messages_proto_goTypes = []interface{}{
+ (*Ping)(nil), // 0: messages.Ping
+ (*Pong)(nil), // 1: messages.Pong
+}
+var file_messages_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_messages_proto_init() }
+func file_messages_proto_init() {
+ if File_messages_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Ping); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Pong); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_messages_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_messages_proto_goTypes,
+ DependencyIndexes: file_messages_proto_depIdxs,
+ MessageInfos: file_messages_proto_msgTypes,
+ }.Build()
+ File_messages_proto = out.File
+ file_messages_proto_rawDesc = nil
+ file_messages_proto_goTypes = nil
+ file_messages_proto_depIdxs = nil
+}
diff --git a/_examples/remote-routing/messages/messages.proto b/_examples/remote-routing/messages/messages.proto
new file mode 100644
index 0000000000000000000000000000000000000000..cd5623c22f230962f9875d550b1c28adc33deae4
--- /dev/null
+++ b/_examples/remote-routing/messages/messages.proto
@@ -0,0 +1,12 @@
+syntax = "proto3";
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-routing/messages";
+package messages;
+
+
+
+message Ping {
+ string User = 1;
+}
+
+message Pong {}
+
diff --git a/_examples/remote-routing/server/main.go b/_examples/remote-routing/server/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..502bf8125fb9d604db4fc6d253f84ec6c2038830
--- /dev/null
+++ b/_examples/remote-routing/server/main.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "net"
+ "runtime"
+ "strconv"
+
+ "remoterouting/messages"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ flagBind = flag.String("bind", "localhost:8100", "Bind to address")
+ flagName = flag.String("name", "node1", "Name")
+
+ system = actor.NewActorSystem()
+ context = system.Root
+)
+
+type remoteActor struct {
+ name string
+ count int
+}
+
+func (a *remoteActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ case *messages.Ping:
+ context.Respond(&messages.Pong{})
+ }
+}
+
+func newRemoteActor(name string) actor.Producer {
+ return func() actor.Actor {
+ return &remoteActor{
+ name: name,
+ }
+ }
+}
+
+func newRemote(bind, name string) {
+ host, _port, err := net.SplitHostPort(bind)
+ if err != nil {
+ panic(err)
+ }
+ port, err := strconv.Atoi(_port)
+ if err != nil {
+ panic(err)
+ }
+
+ r := remote.NewRemote(system, remote.Configure(host, port))
+ r.Start()
+
+ props := actor.
+ PropsFromProducer(newRemoteActor(name),
+ actor.WithMailbox(actor.Bounded(10000)))
+
+ context.SpawnNamed(props, "remote")
+
+ log.Println(name, "Ready")
+}
+
+func main() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+ runtime.GC()
+
+ flag.Parse()
+
+ newRemote(*flagBind, *flagName)
+
+ console.ReadLine()
+}
diff --git a/_examples/remote-routing/start.bat b/_examples/remote-routing/start.bat
new file mode 100644
index 0000000000000000000000000000000000000000..8d7b41b02ac3e7ce1a049df250fcf64b62f2f752
--- /dev/null
+++ b/_examples/remote-routing/start.bat
@@ -0,0 +1,4 @@
+
+start go run server/main.go --name node-1 --bind=127.0.0.1:8101
+start go run server/main.go --name node-2 --bind=127.0.0.1:8102
+start go run client/local.go client/main.go
\ No newline at end of file
diff --git a/_examples/remote-ssl/.gitignore b/_examples/remote-ssl/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..148f7bca678cd1d4f62e97bdf23af520337070be
--- /dev/null
+++ b/_examples/remote-ssl/.gitignore
@@ -0,0 +1,2 @@
+node1
+node2
diff --git a/_examples/remote-ssl/Makefile b/_examples/remote-ssl/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..c5b166feff4c30a54098561acc7824eff9ccbe9c
--- /dev/null
+++ b/_examples/remote-ssl/Makefile
@@ -0,0 +1,25 @@
+all: nodes
+
+clean:
+ @rm -f cert/localhost.key
+ @rm -f cert/localhost.crt
+ @rm -f node1
+ @rm -f node2
+
+ssl:
+ @openssl req \
+ -config cert/localhost.conf \
+ -new \
+ -newkey rsa:4096 \
+ -days 365 \
+ -nodes \
+ -x509 \
+ -subj "/C=US/ST=California/L=SanFrancisco/O=Dis/CN=localhost" \
+ -keyout cert/localhost.key \
+ -out cert/localhost.crt
+
+nodes:
+ go build -o node1 nodes/node1/main.go
+ go build -o node2 nodes/node2/main.go
+
+.PHONY: clean ssl nodes
diff --git a/_examples/remote-ssl/README.md b/_examples/remote-ssl/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2f98d04ece3fbcc937ab610ef393d0bed0b75bc7
--- /dev/null
+++ b/_examples/remote-ssl/README.md
@@ -0,0 +1,91 @@
+# Remote SSL Example
+
+In this example we'll use SSL/TLS to authenticate and encrypt exchanges between remote clients and servers using
+Proto.Actor-Go.
+
+# Requirements
+
+* OpenSSL 1.1.0g+
+* GNU Make 4.1+
+
+# Setup
+
+The `remote` package in Proto.Actor-Go utilizes [gRPC][0] under the hood to enable remote connections between nodes, and
+when creating a server with `remote.Start()` it is possible to pass in several [ServerOption][1] arguments which can be
+used to pass [TransportCredentials][2] to the [gRPC Server][3].
+
+For this example we'll create an SSL certificate using [OpenSSL][4]. You can either use the
+local [Makefile](https://www.gnu.org/software/make/manual/html_node/Introduction.html) provided:
+
+```shell
+make ssl
+```
+
+Or you can do it manually:
+
+```shell
+ @openssl req \
+ -config cert/localhost.conf \
+ -new \
+ -newkey rsa:4096 \
+ -days 365 \
+ -nodes \
+ -x509 \
+ -subj "/C=US/ST=California/L=SanFrancisco/O=Dis/CN=localhost" \
+ -keyout cert/localhost.key \
+ -out cert/localhost.crt
+```
+
+This will place the files `cert/localhost.key` and `cert/localhost.crt` which both nodes will use to communicate with
+one another via TLS.
+
+Now you can use the Makefile to compile the nodes:
+
+```
+make nodes
+```
+
+Or run `go build` manually:
+
+```
+go build -o node1 nodes/node1/main.go
+go build -o node2 nodes/node2/main.go
+```
+
+# Running
+
+For this demo, `node2` will send a message to `node1`, which `node1` will respond to, all over TLS.
+
+You'll want to make sure `node1` is up first:
+
+```shell
+./node1
+```
+
+And then run `node2` in another terminal:
+
+```shell
+./node2
+```
+
+If everything is working properly you should see output like the following from `node1`:
+
+```shell
+127.0.0.1:8090/node1 received SYN from 127.0.0.1:8091/node2
+```
+
+And similarly for `node2`:
+
+```shell
+127.0.0.1:8091/node2 received ACK from 127.0.0.1:8090/node1
+```
+
+[0]:https://google.golang.org/grpc
+
+[1]:https://godoc.org/google.golang.org/grpc#ServerOption
+
+[2]:https://godoc.org/google.golang.org/grpc/credentials#TransportCredentials
+
+[3]:https://godoc.org/google.golang.org/grpc#Server
+
+[4]:https://www.openssl.org/
diff --git a/_examples/remote-ssl/cert/.gitignore b/_examples/remote-ssl/cert/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..aab6efcf8e4f6e4b0cd9eae03d19c5993820b317
--- /dev/null
+++ b/_examples/remote-ssl/cert/.gitignore
@@ -0,0 +1,4 @@
+*
+.*
+!localhost.conf
+!.gitignore
diff --git a/_examples/remote-ssl/cert/localhost.conf b/_examples/remote-ssl/cert/localhost.conf
new file mode 100644
index 0000000000000000000000000000000000000000..b216e2ccedc36756dc2d9378cb696f6fe2cdfc24
--- /dev/null
+++ b/_examples/remote-ssl/cert/localhost.conf
@@ -0,0 +1,43 @@
+[ req ]
+default_bits = 4096
+default_keyfile = localhost.key
+distinguished_name = subject
+req_extensions = extensions
+x509_extensions = extensions
+string_mask = utf8only
+
+[ subject ]
+countryName = Country Name (2 letter code)
+countryName_default = US
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = California
+
+localityName = Locality Name (eg, city)
+localityName_default = San Franscisco
+
+organizationName = Organization Name (eg, company)
+organizationName_default = Example, LLC
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_default = localhost
+
+emailAddress = Email Address
+emailAddress_default = root@localhost
+
+[ extensions ]
+
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+subjectAltName = @alternate_names
+nsComment = "OpenSSL Generated Certificate"
+
+[ alternate_names ]
+
+DNS.1 = localhost
+IP.2 = 127.0.0.1
+IP.3 = ::1
diff --git a/_examples/remote-ssl/go.mod b/_examples/remote-ssl/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..c6d72a22f17b83f7477e281ad1faef1eb3170538
--- /dev/null
+++ b/_examples/remote-ssl/go.mod
@@ -0,0 +1,5 @@
+module remotessl
+
+go 1.16
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
diff --git a/_examples/remote-ssl/messages/messages.proto b/_examples/remote-ssl/messages/messages.proto
new file mode 100644
index 0000000000000000000000000000000000000000..87337061bf7ed13951979ac361c7612df9b762c6
--- /dev/null
+++ b/_examples/remote-ssl/messages/messages.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+package messages;
+import "gitee.com/simplexyz/simpleactor-go/actor/protos.proto";
+
+message EncryptedMessage {
+ string Message = 1;
+}
diff --git a/_examples/remote-ssl/messages/protobuf.sh b/_examples/remote-ssl/messages/protobuf.sh
new file mode 100644
index 0000000000000000000000000000000000000000..aca9857dad351cfeb581608478e558357f15561d
--- /dev/null
+++ b/_examples/remote-ssl/messages/protobuf.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+protoc -I=. -I=$GOPATH/src --gogoslick_out=plugins=grpc:. messages.proto
diff --git a/_examples/remote-watch/Makefile b/_examples/remote-watch/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..a4c35f1d4ba7a4575c1c4a92937addde15c2eec2
--- /dev/null
+++ b/_examples/remote-watch/Makefile
@@ -0,0 +1,11 @@
+start:
+ tmux new-session -d -s eg
+ tmux split-window -t "eg:0" -v
+ tmux send-keys -t "eg:0.0" "go run node2/main.go" Enter
+ tmux send-keys -t "eg:0.1" "go run node1/main.go" Enter
+ tmux attach -t eg
+ tmux kill-session -t eg
+
+
+stop:
+ tmux kill-session -t eg
diff --git a/_examples/remote-watch/go.mod b/_examples/remote-watch/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..70dca78968d31ad2cb007bc83574202c0ce13839
--- /dev/null
+++ b/_examples/remote-watch/go.mod
@@ -0,0 +1,46 @@
+module remotewatch
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ google.golang.org/grpc v1.58.3 // indirect
+)
diff --git a/_examples/remote-watch/go.sum b/_examples/remote-watch/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..af32feca5fbf879242aa12c72a2d4e3f94331558
--- /dev/null
+++ b/_examples/remote-watch/go.sum
@@ -0,0 +1,125 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/remote-watch/messages/build.sh b/_examples/remote-watch/messages/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7f4860098b75fc0c7ca705886c2c10e74e818d31
--- /dev/null
+++ b/_examples/remote-watch/messages/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../../../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. protos.proto
+
diff --git a/_examples/remote-watch/messages/protos.pb.go b/_examples/remote-watch/messages/protos.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..091ee1c0edb74be309e75bd45dc4c75c49bcbe0d
--- /dev/null
+++ b/_examples/remote-watch/messages/protos.pb.go
@@ -0,0 +1,197 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: protos.proto
+
+package messages
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HelloRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *HelloRequest) Reset() {
+ *x = HelloRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{0}
+}
+
+type HelloResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
+}
+
+func (x *HelloResponse) Reset() {
+ *x = HelloResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_protos_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *HelloResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResponse) ProtoMessage() {}
+
+func (x *HelloResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_protos_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResponse.ProtoReflect.Descriptor instead.
+func (*HelloResponse) Descriptor() ([]byte, []int) {
+ return file_protos_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloResponse) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+var File_protos_proto protoreflect.FileDescriptor
+
+var file_protos_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c,
+ 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x0d, 0x48, 0x65, 0x6c, 0x6c,
+ 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x5f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+ 0x65, 0x73, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2d, 0x77, 0x61, 0x74, 0x63, 0x68, 0x2f,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_protos_proto_rawDescOnce sync.Once
+ file_protos_proto_rawDescData = file_protos_proto_rawDesc
+)
+
+func file_protos_proto_rawDescGZIP() []byte {
+ file_protos_proto_rawDescOnce.Do(func() {
+ file_protos_proto_rawDescData = protoimpl.X.CompressGZIP(file_protos_proto_rawDescData)
+ })
+ return file_protos_proto_rawDescData
+}
+
+var file_protos_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_protos_proto_goTypes = []interface{}{
+ (*HelloRequest)(nil), // 0: messages.HelloRequest
+ (*HelloResponse)(nil), // 1: messages.HelloResponse
+}
+var file_protos_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_protos_proto_init() }
+func file_protos_proto_init() {
+ if File_protos_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_protos_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_protos_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*HelloResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_protos_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_protos_proto_goTypes,
+ DependencyIndexes: file_protos_proto_depIdxs,
+ MessageInfos: file_protos_proto_msgTypes,
+ }.Build()
+ File_protos_proto = out.File
+ file_protos_proto_rawDesc = nil
+ file_protos_proto_goTypes = nil
+ file_protos_proto_depIdxs = nil
+}
diff --git a/_examples/remote-watch/messages/protos.proto b/_examples/remote-watch/messages/protos.proto
new file mode 100644
index 0000000000000000000000000000000000000000..aa02b5c7d0f182a2b3616f9e0c4d3c95f9aecd69
--- /dev/null
+++ b/_examples/remote-watch/messages/protos.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+package messages;
+option go_package = "gitee.com/simplexyz/simpleactor-go/_examples/remote-watch/messages";
+
+
+message HelloRequest {}
+message HelloResponse {
+ string Message = 1;
+}
\ No newline at end of file
diff --git a/_examples/remote-watch/node1/main.go b/_examples/remote-watch/node1/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f8e43155fa16c301bdd21eaa80980c42b7e8811
--- /dev/null
+++ b/_examples/remote-watch/node1/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ context = system.Root
+)
+
+func main() {
+ cfg := remote.Configure("127.0.0.1", 8081)
+ r := remote.NewRemote(system, cfg)
+ r.Start()
+
+ timeout := 5 * time.Second
+
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ log.Println("Local actor started")
+ pidResp, err := r.SpawnNamed("127.0.0.1:8080", "myRemote", "remote", timeout)
+ if err != nil {
+ log.Print("Local failed to spawn remote actor")
+ return
+ }
+ log.Println("Local spawned remote actor")
+ ctx.Watch(pidResp.Pid)
+ log.Println("Local is watching remote actor")
+ case *actor.Terminated:
+ log.Printf("Local got terminated message %+v", msg)
+ }
+ })
+
+ context.Spawn(props)
+ console.ReadLine()
+}
diff --git a/_examples/remote-watch/node2/main.go b/_examples/remote-watch/node2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d67f60116d7a512142af7b51cb14b37f49bd9b11
--- /dev/null
+++ b/_examples/remote-watch/node2/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "runtime"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ console "github.com/asynkron/goconsole"
+)
+
+var (
+ system = actor.NewActorSystem()
+ context = system.Root
+)
+
+func main() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+
+ props := actor.PropsFromFunc(func(ctx actor.Context) {})
+ cfg := remote.Configure("127.0.0.1", 8080, remote.WithKinds(remote.NewKind("remote", props)))
+
+ r := remote.NewRemote(system, cfg)
+ r.Register("remote", props)
+ r.Start()
+
+ // empty actor just to have something to remote spawn
+
+ console.ReadLine()
+}
diff --git a/_examples/router-demo/go.mod b/_examples/router-demo/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..5c81ce9968c093ca9c966c4087c7d6aeb4e5e56c
--- /dev/null
+++ b/_examples/router-demo/go.mod
@@ -0,0 +1,40 @@
+module routing
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/router-demo/go.sum b/_examples/router-demo/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..539dcf9dde2b37a66910ee98d6683d9439b9cb7f
--- /dev/null
+++ b/_examples/router-demo/go.sum
@@ -0,0 +1,102 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/router-demo/main.go b/_examples/router-demo/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8afe573d02503548dbfd83d402442415ab79f91
--- /dev/null
+++ b/_examples/router-demo/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "log"
+ "strconv"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/router"
+ console "github.com/asynkron/goconsole"
+)
+
+type myMessage struct{ i int }
+
+func (m *myMessage) Hash() string {
+ return strconv.Itoa(m.i)
+}
+
+func main() {
+ log.Println("Round robin routing:")
+ system := actor.NewActorSystem()
+ rootContext := system.Root
+ act := func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *myMessage:
+ log.Printf("%v got message %d", context.Self(), msg.i)
+ }
+ }
+
+ pid := rootContext.Spawn(router.NewRoundRobinPool(5, actor.WithFunc(act)))
+ for i := 0; i < 10; i++ {
+ rootContext.Send(pid, &myMessage{i})
+ }
+ time.Sleep(1 * time.Second)
+ log.Println("Random routing:")
+ pid = rootContext.Spawn(router.NewRandomPool(5, actor.WithFunc(act)))
+ for i := 0; i < 10; i++ {
+ rootContext.Send(pid, &myMessage{i})
+ }
+ time.Sleep(1 * time.Second)
+ log.Println("ConsistentHash routing:")
+ pid = rootContext.Spawn(router.NewConsistentHashPool(5, actor.WithFunc(act)))
+ for i := 0; i < 10; i++ {
+ rootContext.Send(pid, &myMessage{i})
+ }
+ time.Sleep(1 * time.Second)
+ log.Println("BroadcastPool routing:")
+ pid = rootContext.Spawn(router.NewBroadcastPool(5, actor.WithFunc(act)))
+ for i := 0; i < 10; i++ {
+ rootContext.Send(pid, &myMessage{i})
+ }
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/router-limitconcurrency/go.mod b/_examples/router-limitconcurrency/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..57798845bbfc806e32287807c5ce8e12a12f3891
--- /dev/null
+++ b/_examples/router-limitconcurrency/go.mod
@@ -0,0 +1,40 @@
+module limitconcurrency
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/router-limitconcurrency/go.sum b/_examples/router-limitconcurrency/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..539dcf9dde2b37a66910ee98d6683d9439b9cb7f
--- /dev/null
+++ b/_examples/router-limitconcurrency/go.sum
@@ -0,0 +1,102 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/router-limitconcurrency/main.go b/_examples/router-limitconcurrency/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a238b7cc649a7cf542602212730eff5d66d1553
--- /dev/null
+++ b/_examples/router-limitconcurrency/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "log"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/router"
+ console "github.com/asynkron/goconsole"
+)
+
+type workItem struct{ i int }
+
+const maxConcurrency = 5
+
+func doWork(ctx actor.Context) {
+ if msg, ok := ctx.Message().(*workItem); ok {
+ // this is guaranteed to only execute with a max concurrency level of `maxConcurrency`
+ log.Printf("%v got message %d", ctx.Self(), msg.i)
+ }
+}
+
+func main() {
+ system := actor.NewActorSystem()
+ pid := system.Root.Spawn(router.NewRoundRobinPool(maxConcurrency).Configure(actor.WithFunc(doWork)))
+ for i := 0; i < 1000; i++ {
+ system.Root.Send(pid, &workItem{i})
+ }
+ _, _ = console.ReadLine()
+}
diff --git a/_examples/scheduler/go.mod b/_examples/scheduler/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..e5ae0c59ef164726462b53e262aba61e3f04f9b9
--- /dev/null
+++ b/_examples/scheduler/go.mod
@@ -0,0 +1,39 @@
+module scheduler
+
+go 1.21
+
+toolchain go1.21.2
+
+replace gitee.com/simplexyz/simpleactor-go => ../..
+
+require (
+ gitee.com/simplexyz/simpleactor-go v0.0.0-00010101000000-000000000000
+ github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716
+)
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/orcaman/concurrent-map v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0 // indirect
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/twmb/murmur3 v1.1.6 // indirect
+ go.opentelemetry.io/otel v1.16.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0 // indirect
+ go.opentelemetry.io/otel/metric v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v0.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+)
diff --git a/_examples/scheduler/go.sum b/_examples/scheduler/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..72e1596d2e6bb1dc5da2329b876b42c528bf5f7a
--- /dev/null
+++ b/_examples/scheduler/go.sum
@@ -0,0 +1,100 @@
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716 h1:SgyG4sXkrlalMoCfp20LiNPNhfJS7ez3opNdtihIxPc=
+github.com/asynkron/goconsole v0.0.0-20160504192649-bfa12eebf716/go.mod h1:/zSlF0T2ArAsTG6SVu8d8qlK+19jjudjA3wWsxnGFHg=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/_examples/scheduler/main.go b/_examples/scheduler/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b85b3127606539c5b7c5184e15ed9775cd24d78
--- /dev/null
+++ b/_examples/scheduler/main.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "log"
+ "math/rand"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/scheduler"
+ console "github.com/asynkron/goconsole"
+)
+
+var HelloMessages = []string{
+ "Hello",
+ "Bonjour",
+ "Hola",
+ "Zdravstvuyte",
+ "Nǐn hǎo",
+ "Salve",
+ "Konnichiwa",
+ "Olá",
+}
+
+func main() {
+ var wg sync.WaitGroup
+ wg.Add(5)
+
+ rand.Seed(time.Now().UnixMicro())
+ system := actor.NewActorSystem()
+
+ log.SetFlags(log.LstdFlags | log.Lmicroseconds)
+
+ count := 0
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch t := ctx.Message().(type) {
+ case []string:
+ count++
+ log.Printf("\t%s, counter value: %d", t[rand.Intn(len(t))], count)
+ wg.Done()
+ case string:
+ log.Printf("\t%s\n", t)
+ }
+ })
+
+ pid := system.Root.Spawn(props)
+
+ s := scheduler.NewTimerScheduler(system.Root)
+ cancel := s.SendRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, HelloMessages)
+
+ wg.Wait()
+ cancel()
+
+ wg.Add(100) // add 100 to our waiting group
+ cancel = s.RequestRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, HelloMessages)
+
+ // the following timer will fire before the
+ // wait group is consumed and will stop the scheduler
+ time.Sleep(10 * time.Millisecond)
+ cancel()
+
+ s.SendOnce(1*time.Millisecond, pid, "Hello Once")
+
+ // this message will never show as we cancel it before it can be fired
+ cancel = s.RequestOnce(500*time.Millisecond, pid, "Hello Once Again")
+ time.Sleep(250 * time.Millisecond)
+ cancel()
+
+ _, _ = console.ReadLine()
+}
diff --git a/actor/actor.pb.go b/actor/actor.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..f9b665bd64167e15e03ed5e9eb0f8fe349df844b
--- /dev/null
+++ b/actor/actor.pb.go
@@ -0,0 +1,710 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.31.0
+// protoc v4.24.3
+// source: actor.proto
+
+package actor
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type TerminatedReason int32
+
+const (
+ TerminatedReason_Stopped TerminatedReason = 0
+ TerminatedReason_AddressTerminated TerminatedReason = 1
+ TerminatedReason_NotFound TerminatedReason = 2
+)
+
+// Enum value maps for TerminatedReason.
+var (
+ TerminatedReason_name = map[int32]string{
+ 0: "Stopped",
+ 1: "AddressTerminated",
+ 2: "NotFound",
+ }
+ TerminatedReason_value = map[string]int32{
+ "Stopped": 0,
+ "AddressTerminated": 1,
+ "NotFound": 2,
+ }
+)
+
+func (x TerminatedReason) Enum() *TerminatedReason {
+ p := new(TerminatedReason)
+ *p = x
+ return p
+}
+
+func (x TerminatedReason) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (TerminatedReason) Descriptor() protoreflect.EnumDescriptor {
+ return file_actor_proto_enumTypes[0].Descriptor()
+}
+
+func (TerminatedReason) Type() protoreflect.EnumType {
+ return &file_actor_proto_enumTypes[0]
+}
+
+func (x TerminatedReason) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use TerminatedReason.Descriptor instead.
+func (TerminatedReason) EnumDescriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{0}
+}
+
+type PID struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Address string `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"`
+ ID string `protobuf:"bytes,2,opt,name=ID,proto3" json:"ID,omitempty"`
+ RequestID uint32 `protobuf:"varint,3,opt,name=RequestID,proto3" json:"RequestID,omitempty"`
+
+ //manually added
+ p *Process
+}
+
+func (x *PID) Reset() {
+ *x = PID{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PID) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PID) ProtoMessage() {}
+
+func (x *PID) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PID.ProtoReflect.Descriptor instead.
+func (*PID) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *PID) GetAddress() string {
+ if x != nil {
+ return x.Address
+ }
+ return ""
+}
+
+func (x *PID) GetID() string {
+ if x != nil {
+ return x.ID
+ }
+ return ""
+}
+
+func (x *PID) GetRequestID() uint32 {
+ if x != nil {
+ return x.RequestID
+ }
+ return 0
+}
+
+// user messages
+type PoisonPill struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *PoisonPill) Reset() {
+ *x = PoisonPill{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PoisonPill) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PoisonPill) ProtoMessage() {}
+
+func (x *PoisonPill) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PoisonPill.ProtoReflect.Descriptor instead.
+func (*PoisonPill) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{1}
+}
+
+type DeadLetterResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Target *PID `protobuf:"bytes,1,opt,name=Target,proto3" json:"Target,omitempty"`
+}
+
+func (x *DeadLetterResponse) Reset() {
+ *x = DeadLetterResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeadLetterResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeadLetterResponse) ProtoMessage() {}
+
+func (x *DeadLetterResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeadLetterResponse.ProtoReflect.Descriptor instead.
+func (*DeadLetterResponse) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *DeadLetterResponse) GetTarget() *PID {
+ if x != nil {
+ return x.Target
+ }
+ return nil
+}
+
+// system messages
+type Watch struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Watcher *PID `protobuf:"bytes,1,opt,name=Watcher,proto3" json:"Watcher,omitempty"`
+}
+
+func (x *Watch) Reset() {
+ *x = Watch{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Watch) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Watch) ProtoMessage() {}
+
+func (x *Watch) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Watch.ProtoReflect.Descriptor instead.
+func (*Watch) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *Watch) GetWatcher() *PID {
+ if x != nil {
+ return x.Watcher
+ }
+ return nil
+}
+
+type Unwatch struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Watcher *PID `protobuf:"bytes,1,opt,name=Watcher,proto3" json:"Watcher,omitempty"`
+}
+
+func (x *Unwatch) Reset() {
+ *x = Unwatch{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Unwatch) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Unwatch) ProtoMessage() {}
+
+func (x *Unwatch) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Unwatch.ProtoReflect.Descriptor instead.
+func (*Unwatch) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Unwatch) GetWatcher() *PID {
+ if x != nil {
+ return x.Watcher
+ }
+ return nil
+}
+
+type Terminated struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Who *PID `protobuf:"bytes,1,opt,name=Who,proto3" json:"Who,omitempty"`
+ Why TerminatedReason `protobuf:"varint,2,opt,name=Why,proto3,enum=actor.TerminatedReason" json:"Why,omitempty"`
+}
+
+func (x *Terminated) Reset() {
+ *x = Terminated{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Terminated) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Terminated) ProtoMessage() {}
+
+func (x *Terminated) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Terminated.ProtoReflect.Descriptor instead.
+func (*Terminated) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *Terminated) GetWho() *PID {
+ if x != nil {
+ return x.Who
+ }
+ return nil
+}
+
+func (x *Terminated) GetWhy() TerminatedReason {
+ if x != nil {
+ return x.Why
+ }
+ return TerminatedReason_Stopped
+}
+
+type Stop struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Stop) Reset() {
+ *x = Stop{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Stop) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Stop) ProtoMessage() {}
+
+func (x *Stop) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Stop.ProtoReflect.Descriptor instead.
+func (*Stop) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{6}
+}
+
+type Touch struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Touch) Reset() {
+ *x = Touch{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Touch) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Touch) ProtoMessage() {}
+
+func (x *Touch) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Touch.ProtoReflect.Descriptor instead.
+func (*Touch) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{7}
+}
+
+type Touched struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Who *PID `protobuf:"bytes,1,opt,name=Who,proto3" json:"Who,omitempty"`
+}
+
+func (x *Touched) Reset() {
+ *x = Touched{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_actor_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Touched) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Touched) ProtoMessage() {}
+
+func (x *Touched) ProtoReflect() protoreflect.Message {
+ mi := &file_actor_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Touched.ProtoReflect.Descriptor instead.
+func (*Touched) Descriptor() ([]byte, []int) {
+ return file_actor_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *Touched) GetWho() *PID {
+ if x != nil {
+ return x.Who
+ }
+ return nil
+}
+
+var File_actor_proto protoreflect.FileDescriptor
+
+var file_actor_proto_rawDesc = []byte{
+ 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61,
+ 0x63, 0x74, 0x6f, 0x72, 0x22, 0x4d, 0x0a, 0x03, 0x50, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x41,
+ 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x64,
+ 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+ 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x49, 0x44, 0x22, 0x0c, 0x0a, 0x0a, 0x50, 0x6f, 0x69, 0x73, 0x6f, 0x6e, 0x50, 0x69, 0x6c,
+ 0x6c, 0x22, 0x38, 0x0a, 0x12, 0x44, 0x65, 0x61, 0x64, 0x4c, 0x65, 0x74, 0x74, 0x65, 0x72, 0x52,
+ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65,
+ 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x50, 0x49, 0x44, 0x52, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x2d, 0x0a, 0x05, 0x57,
+ 0x61, 0x74, 0x63, 0x68, 0x12, 0x24, 0x0a, 0x07, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49,
+ 0x44, 0x52, 0x07, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x07, 0x55, 0x6e,
+ 0x77, 0x61, 0x74, 0x63, 0x68, 0x12, 0x24, 0x0a, 0x07, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x07, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x22, 0x55, 0x0a, 0x0a, 0x54,
+ 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x03, 0x57, 0x68, 0x6f,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x03, 0x57, 0x68, 0x6f, 0x12, 0x29, 0x0a, 0x03, 0x57, 0x68, 0x79, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x54, 0x65, 0x72,
+ 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x03, 0x57,
+ 0x68, 0x79, 0x22, 0x06, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x22, 0x07, 0x0a, 0x05, 0x54, 0x6f,
+ 0x75, 0x63, 0x68, 0x22, 0x27, 0x0a, 0x07, 0x54, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x64, 0x12, 0x1c,
+ 0x0a, 0x03, 0x57, 0x68, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x03, 0x57, 0x68, 0x6f, 0x2a, 0x44, 0x0a, 0x10,
+ 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e,
+ 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x15, 0x0a,
+ 0x11, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74,
+ 0x65, 0x64, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64,
+ 0x10, 0x02, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x65, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x79, 0x7a, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65,
+ 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x62, 0x06,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_actor_proto_rawDescOnce sync.Once
+ file_actor_proto_rawDescData = file_actor_proto_rawDesc
+)
+
+func file_actor_proto_rawDescGZIP() []byte {
+ file_actor_proto_rawDescOnce.Do(func() {
+ file_actor_proto_rawDescData = protoimpl.X.CompressGZIP(file_actor_proto_rawDescData)
+ })
+ return file_actor_proto_rawDescData
+}
+
+var file_actor_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_actor_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
+var file_actor_proto_goTypes = []interface{}{
+ (TerminatedReason)(0), // 0: actor.TerminatedReason
+ (*PID)(nil), // 1: actor.PID
+ (*PoisonPill)(nil), // 2: actor.PoisonPill
+ (*DeadLetterResponse)(nil), // 3: actor.DeadLetterResponse
+ (*Watch)(nil), // 4: actor.Watch
+ (*Unwatch)(nil), // 5: actor.Unwatch
+ (*Terminated)(nil), // 6: actor.Terminated
+ (*Stop)(nil), // 7: actor.Stop
+ (*Touch)(nil), // 8: actor.Touch
+ (*Touched)(nil), // 9: actor.Touched
+}
+var file_actor_proto_depIdxs = []int32{
+ 1, // 0: actor.DeadLetterResponse.Target:type_name -> actor.PID
+ 1, // 1: actor.Watch.Watcher:type_name -> actor.PID
+ 1, // 2: actor.Unwatch.Watcher:type_name -> actor.PID
+ 1, // 3: actor.Terminated.Who:type_name -> actor.PID
+ 0, // 4: actor.Terminated.Why:type_name -> actor.TerminatedReason
+ 1, // 5: actor.Touched.Who:type_name -> actor.PID
+ 6, // [6:6] is the sub-list for method output_type
+ 6, // [6:6] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_actor_proto_init() }
+func file_actor_proto_init() {
+ if File_actor_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_actor_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PID); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PoisonPill); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeadLetterResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Watch); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Unwatch); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Terminated); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Stop); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Touch); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_actor_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Touched); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_actor_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 9,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_actor_proto_goTypes,
+ DependencyIndexes: file_actor_proto_depIdxs,
+ EnumInfos: file_actor_proto_enumTypes,
+ MessageInfos: file_actor_proto_msgTypes,
+ }.Build()
+ File_actor_proto = out.File
+ file_actor_proto_rawDesc = nil
+ file_actor_proto_goTypes = nil
+ file_actor_proto_depIdxs = nil
+}
diff --git a/actor/actor.proto b/actor/actor.proto
new file mode 100644
index 0000000000000000000000000000000000000000..c0cc9ebc988a65ffad6d42639b8b4951a7213813
--- /dev/null
+++ b/actor/actor.proto
@@ -0,0 +1,47 @@
+syntax = "proto3";
+package actor;
+option go_package = "gitee.com/simplexyz/simpleactor-go/actor";
+
+message PID {
+ string Address = 1;
+ string Id = 2;
+ uint32 request_id = 3;
+}
+
+//user messages
+message PoisonPill {
+}
+
+message DeadLetterResponse {
+ PID Target = 1;
+}
+
+//system messages
+message Watch {
+ PID Watcher = 1;
+}
+
+message Unwatch {
+ PID Watcher = 1;
+}
+
+message Terminated {
+ PID who = 1;
+ TerminatedReason Why = 2;
+}
+
+enum TerminatedReason {
+ Stopped = 0;
+ AddressTerminated = 1;
+ NotFound = 2;
+}
+
+message Stop {
+}
+
+message Touch {
+}
+
+message Touched {
+ PID who = 1;
+}
\ No newline at end of file
diff --git a/actor/actor_context.go b/actor/actor_context.go
new file mode 100644
index 0000000000000000000000000000000000000000..791c903f6c2203cbfa9fa121ecd53432fbeb3190
--- /dev/null
+++ b/actor/actor_context.go
@@ -0,0 +1,769 @@
+package actor
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/ctxext"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/metrics"
+ "github.com/emirpasic/gods/stacks/linkedliststack"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+)
+
+const (
+ stateAlive int32 = iota
+ stateRestarting
+ stateStopping
+ stateStopped
+)
+
+type actorContextExtras struct {
+ children PIDSet
+ receiveTimeoutTimer *time.Timer
+ rs *RestartStatistics
+ stash *linkedliststack.Stack
+ watchers PIDSet
+ context Context
+ extensions *ctxext.ContextExtensions
+}
+
+func newActorContextExtras(context Context) *actorContextExtras {
+ this := &actorContextExtras{
+ context: context,
+ extensions: ctxext.NewContextExtensions(),
+ }
+
+ return this
+}
+
+func (ctxExt *actorContextExtras) restartStats() *RestartStatistics {
+ // lazy initialize the child restart stats if this is the first time
+ // further mutations are handled within "restart"
+ if ctxExt.rs == nil {
+ ctxExt.rs = NewRestartStatistics()
+ }
+
+ return ctxExt.rs
+}
+
+func (ctxExt *actorContextExtras) initReceiveTimeoutTimer(timer *time.Timer) {
+ ctxExt.receiveTimeoutTimer = timer
+}
+
+func (ctxExt *actorContextExtras) resetReceiveTimeoutTimer(time time.Duration) {
+ if ctxExt.receiveTimeoutTimer == nil {
+ return
+ }
+
+ ctxExt.receiveTimeoutTimer.Reset(time)
+}
+
+func (ctxExt *actorContextExtras) stopReceiveTimeoutTimer() {
+ if ctxExt.receiveTimeoutTimer == nil {
+ return
+ }
+
+ ctxExt.receiveTimeoutTimer.Stop()
+}
+
+func (ctxExt *actorContextExtras) killReceiveTimeoutTimer() {
+ if ctxExt.receiveTimeoutTimer == nil {
+ return
+ }
+
+ ctxExt.receiveTimeoutTimer.Stop()
+ ctxExt.receiveTimeoutTimer = nil
+}
+
+func (ctxExt *actorContextExtras) addChild(pid *PID) {
+ ctxExt.children.Add(pid)
+}
+
+func (ctxExt *actorContextExtras) removeChild(pid *PID) {
+ ctxExt.children.Remove(pid)
+}
+
+func (ctxExt *actorContextExtras) watch(watcher *PID) {
+ ctxExt.watchers.Add(watcher)
+}
+
+func (ctxExt *actorContextExtras) unwatch(watcher *PID) {
+ ctxExt.watchers.Remove(watcher)
+}
+
+type actorContext struct {
+ actor Actor
+ actorSystem *ActorSystem
+ extras *actorContextExtras
+ props *Props
+ parent *PID
+ self *PID
+ receiveTimeout time.Duration
+ messageOrEnvelope interface{}
+ state int32
+}
+
+var (
+ _ SenderContext = &actorContext{}
+ _ ReceiverContext = &actorContext{}
+ _ SpawnerContext = &actorContext{}
+ _ basePart = &actorContext{}
+ _ stopperPart = &actorContext{}
+)
+
+func newActorContext(actorSystem *ActorSystem, props *Props, parent *PID) *actorContext {
+ this := &actorContext{
+ parent: parent,
+ props: props,
+ actorSystem: actorSystem,
+ }
+
+ this.incarnateActor()
+
+ return this
+}
+
+func (ctx *actorContext) ensureExtras() *actorContextExtras {
+ if ctx.extras == nil {
+ ctxd := Context(ctx)
+ if ctx.props != nil && ctx.props.contextDecoratorChain != nil {
+ ctxd = ctx.props.contextDecoratorChain(ctxd)
+ }
+
+ ctx.extras = newActorContextExtras(ctxd)
+ }
+
+ return ctx.extras
+}
+
+//
+// Interface: Context
+//
+
+func (ctx *actorContext) ActorSystem() *ActorSystem {
+ return ctx.actorSystem
+}
+
+func (ctx *actorContext) Parent() *PID {
+ return ctx.parent
+}
+
+func (ctx *actorContext) Self() *PID {
+ return ctx.self
+}
+
+func (ctx *actorContext) Sender() *PID {
+ return UnwrapEnvelopeSender(ctx.messageOrEnvelope)
+}
+
+func (ctx *actorContext) Actor() Actor {
+ return ctx.actor
+}
+
+func (ctx *actorContext) ReceiveTimeout() time.Duration {
+ return ctx.receiveTimeout
+}
+
+func (ctx *actorContext) Children() []*PID {
+ if ctx.extras == nil {
+ return make([]*PID, 0)
+ }
+
+ return ctx.extras.children.Values()
+}
+
+func (ctx *actorContext) Respond(response interface{}) {
+ // If the message is addressed to nil forward it to the dead letter channel
+ if ctx.Sender() == nil {
+ ctx.actorSystem.DeadLetter.SendUserMessage(nil, response)
+
+ return
+ }
+
+ ctx.Send(ctx.Sender(), response)
+}
+
+func (ctx *actorContext) Stash() {
+ extra := ctx.ensureExtras()
+ if extra.stash == nil {
+ extra.stash = linkedliststack.New()
+ }
+
+ extra.stash.Push(ctx.Message())
+}
+
+func (ctx *actorContext) Watch(who *PID) {
+ who.sendSystemMessage(ctx.actorSystem, &Watch{
+ Watcher: ctx.self,
+ })
+}
+
+func (ctx *actorContext) Unwatch(who *PID) {
+ who.sendSystemMessage(ctx.actorSystem, &Unwatch{
+ Watcher: ctx.self,
+ })
+}
+
+func (ctx *actorContext) SetReceiveTimeout(d time.Duration) {
+ if d <= 0 {
+ panic("Duration must be greater than zero")
+ }
+
+ if d == ctx.receiveTimeout {
+ return
+ }
+
+ if d < time.Millisecond {
+ // anything less than 1 millisecond is set to zero
+ d = 0
+ }
+
+ ctx.receiveTimeout = d
+
+ ctx.ensureExtras()
+ ctx.extras.stopReceiveTimeoutTimer()
+
+ if d > 0 {
+ if ctx.extras.receiveTimeoutTimer == nil {
+ ctx.extras.initReceiveTimeoutTimer(time.AfterFunc(d, ctx.receiveTimeoutHandler))
+ } else {
+ ctx.extras.resetReceiveTimeoutTimer(d)
+ }
+ }
+}
+
+func (ctx *actorContext) CancelReceiveTimeout() {
+ if ctx.extras == nil || ctx.extras.receiveTimeoutTimer == nil {
+ return
+ }
+
+ ctx.extras.killReceiveTimeoutTimer()
+ ctx.receiveTimeout = 0
+}
+
+func (ctx *actorContext) receiveTimeoutHandler() {
+ if ctx.extras != nil && ctx.extras.receiveTimeoutTimer != nil {
+ ctx.CancelReceiveTimeout()
+ ctx.Send(ctx.self, receiveTimeoutMessage)
+ }
+}
+
+func (ctx *actorContext) Forward(pid *PID) {
+ if msg, ok := ctx.messageOrEnvelope.(SystemMessage); ok {
+ // SystemMessage cannot be forwarded
+ plog.Error("SystemMessage cannot be forwarded", log.Message(msg))
+
+ return
+ }
+
+ ctx.sendUserMessage(pid, ctx.messageOrEnvelope)
+}
+
+func (ctx *actorContext) ReenterAfter(f *Future, cont func(res interface{}, err error)) {
+ wrapper := func() {
+ cont(f.result, f.err)
+ }
+
+ message := ctx.messageOrEnvelope
+ // invoke the callback when the future completes
+ f.continueWith(func(res interface{}, err error) {
+ // send the wrapped callback as a continuation message to self
+ ctx.self.sendSystemMessage(ctx.actorSystem, &continuation{
+ f: wrapper,
+ message: message,
+ })
+ })
+}
+
+//
+// Interface: sender
+//
+
+func (ctx *actorContext) Message() interface{} {
+ return UnwrapEnvelopeMessage(ctx.messageOrEnvelope)
+}
+
+func (ctx *actorContext) MessageHeader() ReadonlyMessageHeader {
+ return UnwrapEnvelopeHeader(ctx.messageOrEnvelope)
+}
+
+func (ctx *actorContext) Send(pid *PID, message interface{}) {
+ ctx.sendUserMessage(pid, message)
+}
+
+func (ctx *actorContext) sendUserMessage(pid *PID, message interface{}) {
+ if ctx.props.senderMiddlewareChain != nil {
+ ctx.props.senderMiddlewareChain(ctx.ensureExtras().context, pid, WrapEnvelope(message))
+ } else {
+ pid.sendUserMessage(ctx.actorSystem, message)
+ }
+}
+
+func (ctx *actorContext) Request(pid *PID, message interface{}) {
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: ctx.Self(),
+ }
+
+ ctx.sendUserMessage(pid, env)
+}
+
+func (ctx *actorContext) RequestWithCustomSender(pid *PID, message interface{}, sender *PID) {
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: sender,
+ }
+ ctx.sendUserMessage(pid, env)
+}
+
+func (ctx *actorContext) RequestFuture(pid *PID, message interface{}, timeout time.Duration) *Future {
+ future := NewFuture(ctx.actorSystem, timeout)
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: future.PID(),
+ }
+ ctx.sendUserMessage(pid, env)
+
+ return future
+}
+
+//
+// Interface: receiver
+//
+
+func (ctx *actorContext) Receive(envelope *MessageEnvelope) {
+ ctx.messageOrEnvelope = envelope
+ ctx.defaultReceive()
+ ctx.messageOrEnvelope = nil
+}
+
+func (ctx *actorContext) defaultReceive() {
+ switch msg := ctx.Message().(type) {
+ case *PoisonPill:
+ ctx.Stop(ctx.self)
+
+ case AutoRespond:
+ if ctx.props.contextDecoratorChain != nil {
+ ctx.actor.Receive(ctx.ensureExtras().context)
+ } else {
+ ctx.actor.Receive(ctx)
+ }
+
+ res := msg.GetAutoResponse(ctx)
+ ctx.Respond(res)
+
+ default:
+ // are we using decorators, if so, ensure it has been created
+ if ctx.props.contextDecoratorChain != nil {
+ ctx.actor.Receive(ctx.ensureExtras().context)
+
+ return
+ }
+
+ ctx.actor.Receive(ctx)
+ }
+}
+
+//
+// Interface: spawner
+//
+
+func (ctx *actorContext) Spawn(props *Props) *PID {
+ pid, err := ctx.SpawnNamed(props, ctx.actorSystem.ProcessRegistry.NextId())
+ if err != nil {
+ panic(err)
+ }
+
+ return pid
+}
+
+func (ctx *actorContext) SpawnPrefix(props *Props, prefix string) *PID {
+ pid, err := ctx.SpawnNamed(props, prefix+ctx.actorSystem.ProcessRegistry.NextId())
+ if err != nil {
+ panic(err)
+ }
+
+ return pid
+}
+
+func (ctx *actorContext) SpawnNamed(props *Props, name string) (*PID, error) {
+ if props.guardianStrategy != nil {
+ panic(errors.New("props used to spawn child cannot have GuardianStrategy"))
+ }
+
+ var pid *PID
+
+ var err error
+
+ if ctx.props.spawnMiddlewareChain != nil {
+ pid, err = ctx.props.spawnMiddlewareChain(ctx.actorSystem, ctx.self.ID+"/"+name, props, ctx)
+ } else {
+ pid, err = props.spawn(ctx.actorSystem, ctx.self.ID+"/"+name, ctx)
+ }
+
+ if err != nil {
+ return pid, err
+ }
+
+ ctx.ensureExtras().addChild(pid)
+
+ return pid, nil
+}
+
+//
+// Interface: stopper
+//
+
+// Stop will stop actor immediately regardless of existing user messages in mailbox.
+func (ctx *actorContext) Stop(pid *PID) {
+ if ctx.actorSystem.Config.MetricsProvider != nil {
+ metricsSystem, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && metricsSystem.enabled {
+ _ctx := context.Background()
+ if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ instruments.ActorStoppedCount.Add(_ctx, 1, metric.WithAttributes(metricsSystem.CommonLabels(ctx)...))
+ }
+ }
+ }
+
+ pid.ref(ctx.actorSystem).Stop(pid)
+}
+
+// StopFuture will stop actor immediately regardless of existing user messages in mailbox, and return its future.
+func (ctx *actorContext) StopFuture(pid *PID) *Future {
+ future := NewFuture(ctx.actorSystem, 10*time.Second)
+
+ pid.sendSystemMessage(ctx.actorSystem, &Watch{Watcher: future.pid})
+ ctx.Stop(pid)
+
+ return future
+}
+
+// Poison will tell actor to stop after processing current user messages in mailbox.
+func (ctx *actorContext) Poison(pid *PID) {
+ pid.sendUserMessage(ctx.actorSystem, poisonPillMessage)
+}
+
+// PoisonFuture will tell actor to stop after processing current user messages in mailbox, and return its future.
+func (ctx *actorContext) PoisonFuture(pid *PID) *Future {
+ future := NewFuture(ctx.actorSystem, 10*time.Second)
+
+ pid.sendSystemMessage(ctx.actorSystem, &Watch{Watcher: future.pid})
+ ctx.Poison(pid)
+
+ return future
+}
+
+//
+// Interface: MessageInvoker
+//
+
+func (ctx *actorContext) InvokeUserMessage(md interface{}) {
+ if atomic.LoadInt32(&ctx.state) == stateStopped {
+ // already stopped
+ return
+ }
+
+ influenceTimeout := true
+ if ctx.receiveTimeout > 0 {
+ _, influenceTimeout = md.(NotInfluenceReceiveTimeout)
+ influenceTimeout = !influenceTimeout
+
+ if influenceTimeout {
+ ctx.extras.stopReceiveTimeoutTimer()
+ }
+ }
+
+ systemMetrics, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && systemMetrics.enabled {
+ t := time.Now()
+
+ ctx.processMessage(md)
+
+ delta := time.Since(t)
+ _ctx := context.Background()
+
+ if instruments := systemMetrics.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ histogram := instruments.ActorMessageReceiveHistogram
+
+ labels := append(
+ systemMetrics.CommonLabels(ctx),
+ attribute.String("messagetype", fmt.Sprintf("%T", md)),
+ )
+ histogram.Record(_ctx, delta.Seconds(), metric.WithAttributes(labels...))
+ }
+ } else {
+ ctx.processMessage(md)
+ }
+
+ if ctx.receiveTimeout > 0 && influenceTimeout {
+ ctx.extras.resetReceiveTimeoutTimer(ctx.receiveTimeout)
+ }
+}
+
+func (ctx *actorContext) processMessage(m interface{}) {
+ if ctx.props.receiverMiddlewareChain != nil {
+ ctx.props.receiverMiddlewareChain(ctx.ensureExtras().context, WrapEnvelope(m))
+
+ return
+ }
+
+ if ctx.props.contextDecoratorChain != nil {
+ ctx.ensureExtras().context.Receive(WrapEnvelope(m))
+
+ return
+ }
+
+ ctx.messageOrEnvelope = m
+ ctx.defaultReceive()
+ ctx.messageOrEnvelope = nil // release message
+}
+
+func (ctx *actorContext) incarnateActor() {
+ atomic.StoreInt32(&ctx.state, stateAlive)
+ ctx.actor = ctx.props.producer()
+
+ metricsSystem, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && metricsSystem.enabled {
+ _ctx := context.Background()
+ if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ instruments.ActorSpawnCount.Add(_ctx, 1, metric.WithAttributes(metricsSystem.CommonLabels(ctx)...))
+ }
+ }
+}
+
+func (ctx *actorContext) InvokeSystemMessage(message interface{}) {
+ //goland:noinspection GrazieInspection
+ switch msg := message.(type) {
+ case *continuation:
+ ctx.messageOrEnvelope = msg.message // apply the message that was present when we started the await
+ msg.f() // invoke the continuation in the current actor context
+
+ ctx.messageOrEnvelope = nil // release the message
+ case *Started:
+ ctx.InvokeUserMessage(msg) // forward
+ case *Watch:
+ ctx.handleWatch(msg)
+ case *Unwatch:
+ ctx.handleUnwatch(msg)
+ case *Stop:
+ ctx.handleStop()
+ case *Terminated:
+ ctx.handleTerminated(msg)
+ case *Failure:
+ ctx.handleFailure(msg)
+ case *Restart:
+ ctx.handleRestart()
+ default:
+ plog.Error("unknown system message", log.Message(msg))
+ }
+}
+
+func (ctx *actorContext) handleRootFailure(failure *Failure) {
+ defaultSupervisionStrategy.HandleFailure(ctx.actorSystem, ctx, failure.Who, failure.RestartStats, failure.Reason, failure.Message)
+}
+
+func (ctx *actorContext) handleWatch(msg *Watch) {
+ if atomic.LoadInt32(&ctx.state) >= stateStopping {
+ msg.Watcher.sendSystemMessage(ctx.actorSystem, &Terminated{
+ Who: ctx.self,
+ })
+ } else {
+ ctx.ensureExtras().watch(msg.Watcher)
+ }
+}
+
+func (ctx *actorContext) handleUnwatch(msg *Unwatch) {
+ if ctx.extras == nil {
+ return
+ }
+
+ ctx.extras.unwatch(msg.Watcher)
+}
+
+func (ctx *actorContext) handleRestart() {
+ atomic.StoreInt32(&ctx.state, stateRestarting)
+ ctx.InvokeUserMessage(restartingMessage)
+ ctx.stopAllChildren()
+ ctx.tryRestartOrTerminate()
+
+ metricsSystem, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && metricsSystem.enabled {
+ _ctx := context.Background()
+ if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ instruments.ActorRestartedCount.Add(_ctx, 1, metric.WithAttributes(metricsSystem.CommonLabels(ctx)...))
+ }
+ }
+}
+
+// I am stopping.
+func (ctx *actorContext) handleStop() {
+ if atomic.LoadInt32(&ctx.state) >= stateStopping {
+ // already stopping or stopped
+ return
+ }
+
+ atomic.StoreInt32(&ctx.state, stateStopping)
+
+ ctx.InvokeUserMessage(stoppingMessage)
+ ctx.stopAllChildren()
+ ctx.tryRestartOrTerminate()
+}
+
+// child stopped, check if we can stop or restart (if needed).
+func (ctx *actorContext) handleTerminated(msg *Terminated) {
+ if ctx.extras != nil {
+ ctx.extras.removeChild(msg.Who)
+ }
+
+ ctx.InvokeUserMessage(msg)
+ ctx.tryRestartOrTerminate()
+}
+
+// offload the supervision completely to the supervisor strategy.
+func (ctx *actorContext) handleFailure(msg *Failure) {
+ if strategy, ok := ctx.actor.(SupervisorStrategy); ok {
+ strategy.HandleFailure(ctx.actorSystem, ctx, msg.Who, msg.RestartStats, msg.Reason, msg.Message)
+
+ return
+ }
+
+ ctx.props.getSupervisor().HandleFailure(ctx.actorSystem, ctx, msg.Who, msg.RestartStats, msg.Reason, msg.Message)
+}
+
+func (ctx *actorContext) stopAllChildren() {
+ if ctx.extras == nil {
+ return
+ }
+
+ ctx.extras.children.ForEach(func(_ int, pid *PID) {
+ ctx.Stop(pid)
+ })
+}
+
+func (ctx *actorContext) tryRestartOrTerminate() {
+ if ctx.extras != nil && !ctx.extras.children.Empty() {
+ return
+ }
+
+ switch atomic.LoadInt32(&ctx.state) {
+ case stateRestarting:
+ ctx.CancelReceiveTimeout()
+ ctx.restart()
+ case stateStopping:
+ ctx.CancelReceiveTimeout()
+ ctx.finalizeStop()
+ }
+}
+
+func (ctx *actorContext) restart() {
+ ctx.incarnateActor()
+ ctx.self.sendSystemMessage(ctx.actorSystem, resumeMailboxMessage)
+ ctx.InvokeUserMessage(startedMessage)
+
+ if ctx.extras != nil && ctx.extras.stash != nil {
+ for !ctx.extras.stash.Empty() {
+ msg, _ := ctx.extras.stash.Pop()
+ ctx.InvokeUserMessage(msg)
+ }
+ }
+}
+
+func (ctx *actorContext) finalizeStop() {
+ ctx.actorSystem.ProcessRegistry.Remove(ctx.self)
+ ctx.InvokeUserMessage(stoppedMessage)
+
+ otherStopped := &Terminated{Who: ctx.self}
+ // Notify watchers
+ if ctx.extras != nil {
+ ctx.extras.watchers.ForEach(func(i int, pid *PID) {
+ pid.sendSystemMessage(ctx.actorSystem, otherStopped)
+ })
+ }
+ // Notify parent
+ if ctx.parent != nil {
+ ctx.parent.sendSystemMessage(ctx.actorSystem, otherStopped)
+ }
+
+ atomic.StoreInt32(&ctx.state, stateStopped)
+}
+
+//
+// Interface: Supervisor
+//
+
+func (ctx *actorContext) EscalateFailure(reason interface{}, message interface{}) {
+ // debug setting, allows to output supervision failures in console/error level
+ if ctx.actorSystem.Config.DeveloperSupervisionLogging {
+ fmt.Println("[Supervision] Actor:", ctx.self, " failed with message:", message, " exception:", reason)
+ plog.Error("[Supervision]", log.Stringer("actor", ctx.self), log.Object("message", message), log.Object("exception", reason))
+ }
+
+ metricsSystem, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && metricsSystem.enabled {
+ _ctx := context.Background()
+ if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ instruments.ActorFailureCount.Add(_ctx, 1, metric.WithAttributes(metricsSystem.CommonLabels(ctx)...))
+ }
+ }
+
+ failure := &Failure{Reason: reason, Who: ctx.self, RestartStats: ctx.ensureExtras().restartStats(), Message: message}
+
+ ctx.self.sendSystemMessage(ctx.actorSystem, suspendMailboxMessage)
+
+ if ctx.parent == nil {
+ ctx.handleRootFailure(failure)
+ } else {
+ ctx.parent.sendSystemMessage(ctx.actorSystem, failure)
+ }
+}
+
+func (ctx *actorContext) RestartChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(ctx.actorSystem, restartMessage)
+ }
+}
+
+func (ctx *actorContext) StopChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(ctx.actorSystem, stopMessage)
+ }
+}
+
+func (ctx *actorContext) ResumeChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(ctx.actorSystem, resumeMailboxMessage)
+ }
+}
+
+//
+// Miscellaneous
+//
+
+func (ctx *actorContext) GoString() string {
+ return ctx.self.String()
+}
+
+func (ctx *actorContext) String() string {
+ return ctx.self.String()
+}
+
+func (ctx *actorContext) Get(id ctxext.ContextExtensionID) ctxext.ContextExtension {
+ extras := ctx.ensureExtras()
+ ext := extras.extensions.Get(id)
+
+ return ext
+}
+
+func (ctx *actorContext) Set(ext ctxext.ContextExtension) {
+ extras := ctx.ensureExtras()
+ extras.extensions.Set(ext)
+}
diff --git a/actor/actor_context_test.go b/actor/actor_context_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a275d5dcaa1f4d6bd8c5e82540743fe0d20399a8
--- /dev/null
+++ b/actor/actor_context_test.go
@@ -0,0 +1,309 @@
+package actor
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func FuzzSpawnNamed(f *testing.F) {
+ f.Add("parent", "child")
+
+ f.Fuzz(func(t *testing.T, parentName string, childName string) {
+ combined := parentName + "/" + childName
+
+ pid, _ := spawnMockProcess(parentName)
+
+ defer removeMockProcess(pid)
+
+ props := &Props{
+ spawner: func(actorSystem *ActorSystem, id string, _ *Props, _ SpawnerContext) (*PID, error) {
+ assert.Equal(t, combined, id)
+
+ return NewPID(actorSystem.Address(), id), nil
+ },
+ }
+
+ parent := &actorContext{self: NewPID(localAddress, parentName), props: props, actorSystem: system}
+ child, err := parent.SpawnNamed(props, childName)
+ assert.NoError(t, err)
+ assert.Equal(t, parent.Children()[0], child)
+ })
+}
+
+// TestActorContext_Stop verifies if context is stopping and receives a Watch message, it should
+// immediately respond with a Terminated message.
+func TestActorContext_Stop(t *testing.T) {
+ t.Parallel()
+
+ pid, p := spawnMockProcess("foo")
+ defer removeMockProcess(pid)
+
+ other, o := spawnMockProcess("watcher")
+ defer removeMockProcess(other)
+
+ o.On("SendSystemMessage", other, &Terminated{Who: pid})
+
+ props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy()))
+ lc := newActorContext(system, props, nil)
+ lc.self = pid
+ lc.InvokeSystemMessage(&Stop{})
+ lc.InvokeSystemMessage(&Watch{Watcher: other})
+
+ p.AssertExpectations(t)
+ o.AssertExpectations(t)
+}
+
+func TestActorContext_SendMessage_WithSenderMiddleware(t *testing.T) {
+ t.Parallel()
+
+ var wg sync.WaitGroup
+
+ wg.Add(1)
+
+ // Define a local context with no-op sender middleware
+ mw := func(next SenderFunc) SenderFunc {
+ return func(ctx SenderContext, target *PID, envelope *MessageEnvelope) {
+ next(ctx, target, envelope)
+ }
+ }
+
+ props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy()), WithSenderMiddleware(mw))
+ ctx := newActorContext(system, props, nil)
+
+ // Define a receiver to which the local context will send a message
+ var counter int
+
+ receiver := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if _, ok := ctx.Message().(bool); ok {
+ counter++
+ wg.Done()
+ }
+ }))
+
+ // Send a message with Tell
+ // Then wait a little to allow the receiver to process the message
+ // TODO: There should be a better way to wait.
+ timeout := 3 * time.Millisecond
+
+ ctx.Send(receiver, true)
+ wg.Wait()
+ assert.Equal(t, 1, counter)
+
+ // Send a message with Request
+ counter = 0 // Reset the counter
+
+ wg.Add(1)
+ ctx.Request(receiver, true)
+ wg.Wait()
+ assert.Equal(t, 1, counter)
+
+ // Send a message with RequestFuture
+ counter = 0 // Reset the counter
+
+ wg.Add(1)
+
+ _ = ctx.RequestFuture(receiver, true, timeout).Wait()
+ wg.Wait()
+ assert.Equal(t, 1, counter)
+}
+
+func BenchmarkActorContext_ProcessMessageNoMiddleware(b *testing.B) {
+ var m interface{} = 1
+
+ ctx := newActorContext(system, PropsFromFunc(nullReceive), nil)
+ for i := 0; i < b.N; i++ {
+ ctx.processMessage(m)
+ }
+}
+
+func TestActorContext_Respond(t *testing.T) {
+ t.Parallel()
+
+ var wg sync.WaitGroup
+
+ wg.Add(1)
+
+ // Defined a responder actor
+ // It simply echoes a received string.
+ responder := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if m, ok := ctx.Message().(string); ok {
+ ctx.Respond(fmt.Sprintf("Got a string: %v", m))
+ }
+ }))
+
+ // Be prepared to catch a response that the responder will send to nil
+ var gotResponseToNil bool
+
+ deadLetterSubscriber := system.EventStream.Subscribe(func(msg interface{}) {
+ if deadLetter, ok := msg.(*DeadLetterEvent); ok {
+ if deadLetter.PID == nil {
+ gotResponseToNil = true
+ wg.Done()
+ }
+ }
+ })
+
+ // Send a message to the responder using Request
+ // The responder should send something back.
+ timeout := 3 * time.Millisecond
+ res, err := rootContext.RequestFuture(responder, "hello", timeout).Result()
+ assert.Nil(t, err)
+ assert.NotNil(t, res)
+
+ resStr, ok := res.(string)
+ assert.True(t, ok)
+ assert.Equal(t, "Got a string: hello", resStr)
+
+ // Ensure that the responder did not send anything to nil
+ assert.False(t, gotResponseToNil)
+
+ // Send a message using Tell
+ rootContext.Send(responder, "hello")
+
+ // Ensure that the responder actually send something to nil
+ wg.Wait()
+ assert.True(t, gotResponseToNil)
+
+ // Cleanup
+ system.EventStream.Unsubscribe(deadLetterSubscriber)
+}
+
+func TestActorContext_Forward(t *testing.T) {
+ t.Parallel()
+ // Defined a response actor
+ // It simply responds to the string message
+ responder := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if m, ok := ctx.Message().(string); ok {
+ ctx.Respond(fmt.Sprintf("Got a string: %v", m))
+ }
+ }))
+
+ // Defined a forwarder actor
+ // It simply forward the string message to responder
+ forwarder := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if _, ok := ctx.Message().(string); ok {
+ ctx.Forward(responder)
+ }
+ }))
+
+ // Send a message to the responder using Request
+ // The responder should send something back.
+ timeout := 3 * time.Millisecond
+ res, err := rootContext.RequestFuture(forwarder, "hello", timeout).Result()
+ assert.Nil(t, err)
+ assert.NotNil(t, res)
+
+ resStr, ok := res.(string)
+ assert.True(t, ok)
+ assert.Equal(t, "Got a string: hello", resStr)
+}
+
+func BenchmarkActorContext_ProcessMessageWithMiddleware(b *testing.B) {
+ var m interface{} = 1
+
+ fn := func(next ReceiverFunc) ReceiverFunc {
+ return func(ctx ReceiverContext, env *MessageEnvelope) {
+ next(ctx, env)
+ }
+ }
+
+ props := PropsFromProducer(nullProducer, WithSupervisor(DefaultSupervisorStrategy()), WithReceiverMiddleware(fn))
+ ctx := newActorContext(system, props, nil)
+
+ for i := 0; i < b.N; i++ {
+ ctx.processMessage(m)
+ }
+}
+
+func benchmarkactorcontextSpawnwithmiddlewaren(n int, b *testing.B) {
+ middlewareFn := func(next SenderFunc) SenderFunc {
+ return func(ctx SenderContext, pid *PID, env *MessageEnvelope) {
+ next(ctx, pid, env)
+ }
+ }
+
+ props := PropsFromProducer(nullProducer)
+ for i := 0; i < n; i++ {
+ props = props.Configure(WithSenderMiddleware(middlewareFn))
+ }
+
+ system := NewActorSystem()
+ parent := &actorContext{self: NewPID(localAddress, "foo"), props: props, actorSystem: system}
+
+ for i := 0; i < b.N; i++ {
+ parent.Spawn(props)
+ }
+}
+
+func BenchmarkActorContext_SpawnWithMiddleware0(b *testing.B) {
+ benchmarkactorcontextSpawnwithmiddlewaren(0, b)
+}
+
+func BenchmarkActorContext_SpawnWithMiddleware1(b *testing.B) {
+ benchmarkactorcontextSpawnwithmiddlewaren(1, b)
+}
+
+func BenchmarkActorContext_SpawnWithMiddleware2(b *testing.B) {
+ benchmarkactorcontextSpawnwithmiddlewaren(2, b)
+}
+
+func BenchmarkActorContext_SpawnWithMiddleware5(b *testing.B) {
+ benchmarkactorcontextSpawnwithmiddlewaren(5, b)
+}
+
+func TestActorContinueFutureInActor(t *testing.T) {
+ t.Parallel()
+
+ pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if ctx.Message() == "request" {
+ ctx.Respond("done")
+ }
+ if ctx.Message() == "start" {
+ f := ctx.RequestFuture(ctx.Self(), "request", 5*time.Second)
+ ctx.ReenterAfter(f, func(res interface{}, err error) {
+ ctx.Respond(res)
+ })
+ }
+ }))
+ res, err := rootContext.RequestFuture(pid, "start", time.Second).Result()
+ assert.NoError(t, err)
+ assert.Equal(t, "done", res)
+}
+
+type dummyAutoRespond struct{}
+
+func (*dummyAutoRespond) GetAutoResponse(_ Context) interface{} {
+ return &dummyResponse{}
+}
+
+func TestActorContextAutoRespondMessage(t *testing.T) {
+ t.Parallel()
+
+ pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {}))
+
+ var msg AutoRespond = &dummyAutoRespond{}
+
+ res, err := rootContext.RequestFuture(pid, msg, 1*time.Second).Result()
+ assert.NoError(t, err)
+ assert.IsType(t, &dummyResponse{}, res)
+}
+
+func TestActorContextAutoRespondTouchedMessage(t *testing.T) {
+ t.Parallel()
+
+ pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {}))
+
+ var msg AutoRespond = &Touch{}
+
+ res, err := rootContext.RequestFuture(pid, msg, 1*time.Second).Result()
+
+ res2, _ := res.(*Touched)
+
+ assert.NoError(t, err)
+ assert.IsType(t, &Touched{}, res)
+ assert.True(t, res2.Who.Equal(pid))
+}
diff --git a/actor/actor_example_test.go b/actor/actor_example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8de28baed1e9c0f3c9a7b3de41f9b8e0552aee72
--- /dev/null
+++ b/actor/actor_example_test.go
@@ -0,0 +1,67 @@
+package actor_test
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Demonstrates how to create an actor using a function literal and how to send a message asynchronously
+func Example() {
+ context := system.Root
+ props := actor.PropsFromFunc(func(c actor.Context) {
+ if msg, ok := c.Message().(string); ok {
+ fmt.Println(msg) // outputs "Hello World"
+ }
+ })
+
+ pid := context.Spawn(props)
+
+ context.Send(pid, "Hello World")
+ time.Sleep(time.Millisecond * 100)
+
+ _ = context.StopFuture(pid).Wait() // wait for the actor to stop
+
+ // Output: Hello World
+}
+
+// Demonstrates how to send a message from one actor to another and for the caller to wait for a response before
+// proceeding
+func Example_synchronous() {
+ var wg sync.WaitGroup
+
+ wg.Add(1)
+
+ // callee will wait for the PING message
+ callee := system.Root.Spawn(actor.PropsFromFunc(func(c actor.Context) {
+ if msg, ok := c.Message().(string); ok {
+ fmt.Println(msg) // outputs PING
+ c.Respond("PONG")
+ }
+ }))
+
+ // caller will send a PING message and wait for the PONG
+ caller := system.Root.Spawn(actor.PropsFromFunc(func(c actor.Context) {
+ switch msg := c.Message().(type) {
+ // the first message an actor receives after it has started
+ case *actor.Started:
+ // send a PING to the callee, and specify the response
+ // is sent to Self, which is this actor'pids PID
+ c.Request(callee, "PING")
+
+ case string:
+ fmt.Println(msg) // PONG
+ wg.Done()
+ }
+ }))
+
+ wg.Wait()
+ _ = system.Root.StopFuture(callee).Wait()
+ _ = system.Root.StopFuture(caller).Wait()
+
+ // Output:
+ // PING
+ // PONG
+}
diff --git a/actor/actor_process.go b/actor/actor_process.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8483fb077ae75823244e66a01187243e3d6ee5e
--- /dev/null
+++ b/actor/actor_process.go
@@ -0,0 +1,31 @@
+package actor
+
+import (
+ "sync/atomic"
+)
+
+type ActorProcess struct {
+ mailbox Mailbox
+ dead int32
+}
+
+var _ Process = &ActorProcess{}
+
+func NewActorProcess(mailbox Mailbox) *ActorProcess {
+ return &ActorProcess{
+ mailbox: mailbox,
+ }
+}
+
+func (ref *ActorProcess) SendUserMessage(_ *PID, message interface{}) {
+ ref.mailbox.PostUserMessage(message)
+}
+
+func (ref *ActorProcess) SendSystemMessage(_ *PID, message interface{}) {
+ ref.mailbox.PostSystemMessage(message)
+}
+
+func (ref *ActorProcess) Stop(pid *PID) {
+ atomic.StoreInt32(&ref.dead, 1)
+ ref.SendSystemMessage(pid, stopMessage)
+}
diff --git a/actor/actor_system.go b/actor/actor_system.go
new file mode 100644
index 0000000000000000000000000000000000000000..09b9f2de3d2c61e7118bb0ecadacb9a43ae70fbd
--- /dev/null
+++ b/actor/actor_system.go
@@ -0,0 +1,86 @@
+package actor
+
+import (
+ "net"
+ "strconv"
+
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/extensions"
+ "github.com/lithammer/shortuuid/v4"
+)
+
+//goland:noinspection GoNameStartsWithPackageName
+type ActorSystem struct {
+ ProcessRegistry *ProcessRegistryValue
+ Root *RootContext
+ EventStream *eventstream.EventStream
+ Guardians *guardiansValue
+ DeadLetter *deadLetterProcess
+ Extensions *extensions.Extensions
+ Config *Config
+ ID string
+ stopper chan struct{}
+}
+
+func (as *ActorSystem) NewLocalPID(id string) *PID {
+ return NewPID(as.ProcessRegistry.Address, id)
+}
+
+func (as *ActorSystem) Address() string {
+ return as.ProcessRegistry.Address
+}
+
+func (as *ActorSystem) GetHostPort() (host string, port int, err error) {
+ addr := as.ProcessRegistry.Address
+ if h, p, e := net.SplitHostPort(addr); e != nil {
+ if addr != localAddress {
+ err = e
+ }
+
+ host = localAddress
+ port = -1
+ } else {
+ host = h
+ port, err = strconv.Atoi(p)
+ }
+
+ return
+}
+
+func (as *ActorSystem) Shutdown() {
+ close(as.stopper)
+}
+
+func (as *ActorSystem) IsStopped() bool {
+ select {
+ case <-as.stopper:
+ return true
+ default:
+ return false
+ }
+}
+
+func NewActorSystem(options ...ConfigOption) *ActorSystem {
+ config := Configure(options...)
+
+ return NewActorSystemWithConfig(config)
+}
+
+func NewActorSystemWithConfig(config *Config) *ActorSystem {
+ system := &ActorSystem{}
+ system.ID = shortuuid.New()
+ system.Config = config
+ system.ProcessRegistry = NewProcessRegistry(system)
+ system.Root = NewRootContext(system, EmptyMessageHeader)
+ system.Guardians = NewGuardians(system)
+ system.EventStream = eventstream.NewEventStream()
+ system.DeadLetter = NewDeadLetter(system)
+ system.Extensions = extensions.NewExtensions()
+ SubscribeSupervision(system)
+ system.Extensions.Register(NewMetrics(config.MetricsProvider))
+
+ system.ProcessRegistry.Add(NewEventStreamProcess(system), "eventstream")
+ system.stopper = make(chan struct{})
+
+ return system
+}
diff --git a/actor/auto_respond.go b/actor/auto_respond.go
new file mode 100644
index 0000000000000000000000000000000000000000..22031c49df5074f3d0367981713f15c225bdfcdc
--- /dev/null
+++ b/actor/auto_respond.go
@@ -0,0 +1,5 @@
+package actor
+
+type AutoRespond interface {
+ GetAutoResponse(context Context) interface{}
+}
diff --git a/actor/behavior.go b/actor/behavior.go
new file mode 100644
index 0000000000000000000000000000000000000000..6eb450ec6b64df1c5b26369a11df33ce766f1fa8
--- /dev/null
+++ b/actor/behavior.go
@@ -0,0 +1,73 @@
+package actor
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+type Behavior []ReceiveFunc
+
+func NewBehavior() Behavior {
+ return make(Behavior, 0)
+}
+
+func (b *Behavior) Become(receive ReceiveFunc) {
+ b.clear()
+ b.push(receive)
+}
+
+func (b *Behavior) BecomeStacked(receive ReceiveFunc) {
+ b.push(receive)
+}
+
+func (b *Behavior) UnbecomeStacked() {
+ b.pop()
+}
+
+func (b *Behavior) Receive(context Context) {
+ behavior, ok := b.peek()
+ if ok {
+ behavior(context)
+ } else {
+ plog.Error("empty behavior called", log.Stringer("pid", context.Self()))
+ }
+}
+
+func (b *Behavior) clear() {
+ if len(*b) == 0 {
+ return
+ }
+
+ for i := range *b {
+ (*b)[i] = nil
+ }
+
+ *b = (*b)[:0]
+}
+
+func (b *Behavior) peek() (v ReceiveFunc, ok bool) {
+ if l := b.len(); l > 0 {
+ ok = true
+ v = (*b)[l-1]
+ }
+
+ return
+}
+
+func (b *Behavior) push(v ReceiveFunc) {
+ *b = append(*b, v)
+}
+
+func (b *Behavior) pop() (v ReceiveFunc, ok bool) {
+ if l := b.len(); l > 0 {
+ l--
+
+ ok = true
+ v = (*b)[l]
+ (*b)[l] = nil
+ *b = (*b)[:l]
+ }
+
+ return
+}
+
+func (b *Behavior) len() int {
+ return len(*b)
+}
diff --git a/actor/behavior_test.go b/actor/behavior_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a813e30aba3800eaac7fc0bf082182cf8303a8d
--- /dev/null
+++ b/actor/behavior_test.go
@@ -0,0 +1,63 @@
+package actor
+
+import (
+ "testing"
+)
+
+type BehaviorMessage struct{}
+
+type EchoSetBehaviorActor struct {
+ behavior Behavior
+}
+
+func NewEchoBehaviorActor() Actor {
+ state := &EchoSetBehaviorActor{
+ behavior: NewBehavior(),
+ }
+ state.behavior.Become(state.one)
+
+ return state
+}
+
+func (state *EchoSetBehaviorActor) Receive(context Context) {
+ state.behavior.Receive(context)
+}
+
+func (state *EchoSetBehaviorActor) one(context Context) {
+ if _, ok := context.Message().(BehaviorMessage); ok {
+ state.behavior.Become(state.other)
+ }
+}
+
+func (EchoSetBehaviorActor) other(context Context) {
+ if _, ok := context.Message().(EchoRequest); ok {
+ context.Respond(EchoResponse{})
+ }
+}
+
+func TestActorCanSetBehavior(t *testing.T) {
+ pid := rootContext.Spawn(PropsFromProducer(NewEchoBehaviorActor))
+ defer rootContext.Stop(pid)
+ rootContext.Send(pid, BehaviorMessage{})
+ fut := rootContext.RequestFuture(pid, EchoRequest{}, testTimeout)
+ assertFutureSuccess(fut, t)
+}
+
+type PopBehaviorMessage struct{}
+
+func NewEchoUnbecomeActor() Actor {
+ state := &EchoSetBehaviorActor{
+ behavior: NewBehavior(),
+ }
+ state.behavior.Become(state.one)
+
+ return state
+}
+
+func TestActorCanPopBehavior(t *testing.T) {
+ a := rootContext.Spawn(PropsFromProducer(NewEchoUnbecomeActor))
+ rootContext.Send(a, BehaviorMessage{})
+ rootContext.Send(a, PopBehaviorMessage{})
+ fut := rootContext.RequestFuture(a, EchoRequest{}, testTimeout)
+ assertFutureSuccess(fut, t)
+}
diff --git a/actor/behaviorlogic_test.go b/actor/behaviorlogic_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe6b27791feabfdb9bbcde0e94325f2eac349ddd
--- /dev/null
+++ b/actor/behaviorlogic_test.go
@@ -0,0 +1,93 @@
+package actor
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBehavior_Len(t *testing.T) {
+ var bs Behavior
+
+ assert.Len(t, bs, 0)
+ bs.push(func(Context) {})
+ bs.push(func(Context) {})
+ assert.Len(t, bs, 2)
+}
+
+func TestBehavior_Push(t *testing.T) {
+ var bs Behavior
+
+ assert.Len(t, bs, 0)
+ bs.push(func(Context) {})
+ assert.Len(t, bs, 1)
+ bs.push(func(Context) {})
+ assert.Len(t, bs, 2)
+}
+
+func TestBehavior_Clear(t *testing.T) {
+ var bs Behavior
+
+ bs.push(func(Context) {})
+ bs.push(func(Context) {})
+ assert.Len(t, bs, 2)
+ bs.clear()
+ assert.Len(t, bs, 0)
+}
+
+func TestBehavior_Peek(t *testing.T) {
+ called := 0
+ fn1 := ReceiveFunc(func(Context) { called = 1 })
+ fn2 := ReceiveFunc(func(Context) { called = 2 })
+
+ cases := []struct {
+ items []ReceiveFunc
+ expected int
+ }{
+ {[]ReceiveFunc{fn1, fn2}, 2},
+ {[]ReceiveFunc{fn2, fn1}, 1},
+ }
+
+ for _, tc := range cases {
+ t.Run("", func(t *testing.T) {
+ var bs Behavior
+ for _, fn := range tc.items {
+ bs.push(fn)
+ }
+ a, _ := bs.peek()
+ a(nil)
+ assert.Equal(t, tc.expected, called)
+ })
+ }
+}
+
+func TestBehaviorStack_Pop_ExpectedOrder(t *testing.T) {
+ called := 0
+ fn1 := ReceiveFunc(func(Context) { called = 1 })
+ fn2 := ReceiveFunc(func(Context) { called = 2 })
+
+ cases := []struct {
+ items []ReceiveFunc
+ expected []int
+ }{
+ {[]ReceiveFunc{fn1, fn2}, []int{2, 1}},
+ {[]ReceiveFunc{fn2, fn1}, []int{1, 2}},
+ }
+
+ for i, tc := range cases {
+ t.Run("order "+strconv.Itoa(i), func(t *testing.T) {
+ var bs Behavior
+ for _, fn := range tc.items {
+ bs.push(fn)
+ }
+
+ for _, e := range tc.expected {
+ a, _ := bs.pop()
+ a(nil)
+ assert.Equal(t, e, called)
+ called = 0
+ }
+ })
+ }
+}
diff --git a/actor/bounded.go b/actor/bounded.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c6eeee8c7b3af565feb563ad14c2c34ee85e4f9
--- /dev/null
+++ b/actor/bounded.go
@@ -0,0 +1,56 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+ rbqueue "github.com/Workiva/go-datastructures/queue"
+)
+
+type boundedMailboxQueue struct {
+ userMailbox *rbqueue.RingBuffer
+ dropping bool
+}
+
+func (q *boundedMailboxQueue) Push(m interface{}) {
+ if q.dropping {
+ if q.userMailbox.Len() > 0 && q.userMailbox.Cap()-1 == q.userMailbox.Len() {
+ _, _ = q.userMailbox.Get()
+ }
+ }
+
+ _ = q.userMailbox.Put(m)
+}
+
+func (q *boundedMailboxQueue) Pop() interface{} {
+ if q.userMailbox.Len() > 0 {
+ m, _ := q.userMailbox.Get()
+
+ return m
+ }
+
+ return nil
+}
+
+// Bounded returns a producer which creates a bounded mailbox of the specified size.
+func Bounded(size int, mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return bounded(size, false, mailboxStats...)
+}
+
+// BoundedDropping returns a producer which creates a bounded mailbox of the specified size that drops front element on push.
+func BoundedDropping(size int, mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return bounded(size, true, mailboxStats...)
+}
+
+func bounded(size int, dropping bool, mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return func() Mailbox {
+ q := &boundedMailboxQueue{
+ userMailbox: rbqueue.NewRingBuffer(uint64(size)),
+ dropping: dropping,
+ }
+
+ return &defaultMailbox{
+ systemMailbox: mpsc.New(),
+ userMailbox: q,
+ middlewares: mailboxStats,
+ }
+ }
+}
diff --git a/actor/child_restart_stats.go b/actor/child_restart_stats.go
new file mode 100644
index 0000000000000000000000000000000000000000..082db0fff45ee5b7492eabe5f77502d5c9b7fc5c
--- /dev/null
+++ b/actor/child_restart_stats.go
@@ -0,0 +1,48 @@
+package actor
+
+import (
+ "time"
+)
+
+// RestartStatistics keeps track of how many times an actor have restarted and when
+type RestartStatistics struct {
+ failureTimes []time.Time
+}
+
+// NewRestartStatistics construct a RestartStatistics
+func NewRestartStatistics() *RestartStatistics {
+ return &RestartStatistics{[]time.Time{}}
+}
+
+// FailureCount returns failure count
+func (rs *RestartStatistics) FailureCount() int {
+ return len(rs.failureTimes)
+}
+
+// Fail increases the associated actors' failure count
+func (rs *RestartStatistics) Fail() {
+ rs.failureTimes = append(rs.failureTimes, time.Now())
+}
+
+// Reset the associated actors' failure count
+func (rs *RestartStatistics) Reset() {
+ rs.failureTimes = []time.Time{}
+}
+
+// NumberOfFailures returns number of failures within a given duration
+func (rs *RestartStatistics) NumberOfFailures(withinDuration time.Duration) int {
+ if withinDuration == 0 {
+ return len(rs.failureTimes)
+ }
+
+ num := 0
+ currTime := time.Now()
+
+ for _, t := range rs.failureTimes {
+ if currTime.Sub(t) < withinDuration {
+ num++
+ }
+ }
+
+ return num
+}
diff --git a/actor/child_test.go b/actor/child_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cd66fc93b619e11427656ad67cc610fbd831ed6
--- /dev/null
+++ b/actor/child_test.go
@@ -0,0 +1,139 @@
+package actor
+
+import (
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type (
+ CreateChildMessage struct{}
+ GetChildCountRequest struct{}
+ GetChildCountResponse struct{ ChildCount int }
+ CreateChildActor struct{}
+)
+
+func (*CreateChildActor) Receive(context Context) {
+ switch context.Message().(type) {
+ case CreateChildMessage:
+ context.Spawn(PropsFromProducer(NewBlackHoleActor))
+ case GetChildCountRequest:
+ reply := GetChildCountResponse{ChildCount: len(context.Children())}
+ context.Respond(reply)
+ }
+}
+
+func NewCreateChildActor() Actor {
+ return &CreateChildActor{}
+}
+
+func TestActorCanCreateChildren(t *testing.T) {
+ a := rootContext.Spawn(PropsFromProducer(NewCreateChildActor))
+ defer rootContext.Stop(a)
+ expected := 10
+ for i := 0; i < expected; i++ {
+ rootContext.Send(a, CreateChildMessage{})
+ }
+ fut := rootContext.RequestFuture(a, GetChildCountRequest{}, testTimeout)
+ response := assertFutureSuccess(fut, t)
+ assert.Equal(t, expected, response.(GetChildCountResponse).ChildCount)
+}
+
+type CreateChildThenStopActor struct {
+ replyTo *PID
+}
+
+type GetChildCountMessage2 struct {
+ ReplyDirectly *PID
+ ReplyAfterStop *PID
+}
+
+func (state *CreateChildThenStopActor) Receive(context Context) {
+ switch msg := context.Message().(type) {
+ case CreateChildMessage:
+ context.Spawn(PropsFromProducer(NewBlackHoleActor))
+ case GetChildCountMessage2:
+ context.Send(msg.ReplyDirectly, true)
+ state.replyTo = msg.ReplyAfterStop
+ case *Stopped:
+ reply := GetChildCountResponse{ChildCount: len(context.Children())}
+ context.Send(state.replyTo, reply)
+ }
+}
+
+func NewCreateChildThenStopActor() Actor {
+ return &CreateChildThenStopActor{}
+}
+
+func TestActorCanStopChildren(t *testing.T) {
+ actor := rootContext.Spawn(PropsFromProducer(NewCreateChildThenStopActor))
+ count := 10
+ for i := 0; i < count; i++ {
+ rootContext.Send(actor, CreateChildMessage{})
+ }
+
+ future := NewFuture(system, testTimeout)
+ future2 := NewFuture(system, testTimeout)
+ rootContext.Send(actor, GetChildCountMessage2{ReplyDirectly: future.PID(), ReplyAfterStop: future2.PID()})
+
+ // wait for the actor to reply to the first responsePID
+ assertFutureSuccess(future, t)
+
+ // then send a stop command
+ rootContext.Stop(actor)
+
+ // wait for the actor to stop and get the result from the stopped handler
+ response := assertFutureSuccess(future2, t)
+ // we should have 0 children when the actor is stopped
+ assert.Equal(t, 0, response.(GetChildCountResponse).ChildCount)
+}
+
+func TestActorReceivesTerminatedFromWatched(t *testing.T) {
+ child := rootContext.Spawn(PropsFromFunc(nullReceive))
+ future := NewFuture(system, testTimeout)
+ var wg sync.WaitGroup
+ wg.Add(1)
+
+ var r ReceiveFunc = func(c Context) {
+ switch msg := c.Message().(type) {
+ case *Started:
+ c.Watch(child)
+ wg.Done()
+
+ case *Terminated:
+ ac := c.(*actorContext)
+ if msg.Who.Equal(child) && ac.ensureExtras().watchers.Empty() {
+ c.Send(future.PID(), true)
+ }
+ }
+ }
+
+ rootContext.Spawn(PropsFromFunc(r))
+ wg.Wait()
+ rootContext.Stop(child)
+
+ assertFutureSuccess(future, t)
+}
+
+func TestFutureDoesTimeout(t *testing.T) {
+ pid := rootContext.Spawn(PropsFromFunc(nullReceive))
+ _, err := rootContext.RequestFuture(pid, "", time.Millisecond).Result()
+ assert.EqualError(t, err, ErrTimeout.Error())
+}
+
+func TestFutureDoesNotTimeout(t *testing.T) {
+ var r ReceiveFunc = func(c Context) {
+ if _, ok := c.Message().(string); !ok {
+ return
+ }
+
+ time.Sleep(50 * time.Millisecond)
+ c.Respond("foo")
+ }
+ pid := rootContext.Spawn(PropsFromFunc(r))
+ reply, err := rootContext.RequestFuture(pid, "", 2*time.Second).Result()
+ assert.NoError(t, err)
+ assert.Equal(t, "foo", reply)
+}
diff --git a/actor/common_test.go b/actor/common_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..84dd17e66ed31cc883ea0f9ec75e1b3844cd85f0
--- /dev/null
+++ b/actor/common_test.go
@@ -0,0 +1,213 @@
+package actor
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/ctxext"
+
+ "github.com/stretchr/testify/mock"
+)
+
+var (
+ nullProducer Producer = func() Actor { return nullReceive }
+ nullReceive ReceiveFunc = func(Context) {}
+ system = NewActorSystem()
+ rootContext = system.Root
+)
+
+// mockContext
+type mockContext struct {
+ mock.Mock
+}
+
+//
+// Interface: Context
+//
+
+func (m *mockContext) ActorSystem() *ActorSystem {
+ args := m.Called()
+ return args.Get(0).(*ActorSystem)
+}
+
+func (m *mockContext) Get(id ctxext.ContextExtensionID) ctxext.ContextExtension {
+ args := m.Called(id)
+ return args.Get(0).(ctxext.ContextExtension)
+}
+
+func (m *mockContext) Set(ext ctxext.ContextExtension) {
+ m.Called(ext)
+}
+
+func (m *mockContext) Parent() *PID {
+ args := m.Called()
+ return args.Get(0).(*PID)
+}
+
+func (m *mockContext) Self() *PID {
+ args := m.Called()
+ return args.Get(0).(*PID)
+}
+
+func (m *mockContext) Sender() *PID {
+ args := m.Called()
+ return args.Get(0).(*PID)
+}
+
+func (m *mockContext) Actor() Actor {
+ args := m.Called()
+ return args.Get(0).(Actor)
+}
+
+func (m *mockContext) ReceiveTimeout() time.Duration {
+ args := m.Called()
+ return args.Get(0).(time.Duration)
+}
+
+func (m *mockContext) Children() []*PID {
+ args := m.Called()
+ return args.Get(0).([]*PID)
+}
+
+func (m *mockContext) Respond(response interface{}) {
+ m.Called(response)
+}
+
+func (m *mockContext) Stash() {
+ m.Called()
+}
+
+func (m *mockContext) Watch(pid *PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) Unwatch(pid *PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) SetReceiveTimeout(d time.Duration) {
+ m.Called(d)
+}
+
+func (m *mockContext) CancelReceiveTimeout() {
+ m.Called()
+}
+
+func (m *mockContext) Forward(_ *PID) {
+ m.Called()
+}
+
+func (m *mockContext) ReenterAfter(f *Future, cont func(res interface{}, err error)) {
+ m.Called(f, cont)
+}
+
+//
+// Interface: SenderContext
+//
+
+func (m *mockContext) Message() interface{} {
+ args := m.Called()
+
+ return args.Get(0)
+}
+
+func (m *mockContext) MessageHeader() ReadonlyMessageHeader {
+ args := m.Called()
+
+ return args.Get(0).(ReadonlyMessageHeader)
+}
+
+func (m *mockContext) Send(_ *PID, _ interface{}) {
+ m.Called()
+}
+
+func (m *mockContext) Request(pid *PID, message interface{}) {
+ args := m.Called()
+
+ p, _ := system.ProcessRegistry.Get(pid)
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: args.Get(0).(*PID),
+ }
+ p.SendUserMessage(pid, env)
+}
+
+func (m *mockContext) RequestWithCustomSender(pid *PID, message interface{}, sender *PID) {
+ m.Called()
+
+ p, _ := system.ProcessRegistry.Get(pid)
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: sender,
+ }
+ p.SendUserMessage(pid, env)
+}
+
+func (m *mockContext) RequestFuture(_ *PID, _ interface{}, _ time.Duration) *Future {
+ args := m.Called()
+
+ return args.Get(0).(*Future)
+}
+
+//
+// Interface: ReceiverContext
+//
+
+func (m *mockContext) Receive(envelope *MessageEnvelope) {
+ m.Called(envelope)
+}
+
+//
+// Interface: SpawnerContext
+//
+
+func (m *mockContext) Spawn(p *Props) *PID {
+ args := m.Called(p)
+ return args.Get(0).(*PID)
+}
+
+func (m *mockContext) SpawnPrefix(p *Props, prefix string) *PID {
+ args := m.Called(p, prefix)
+
+ return args.Get(0).(*PID)
+}
+
+func (m *mockContext) SpawnNamed(p *Props, name string) (*PID, error) {
+ args := m.Called(p, name)
+
+ return args.Get(0).(*PID), args.Get(1).(error)
+}
+
+// mockProcess
+type mockProcess struct {
+ mock.Mock
+}
+
+func spawnMockProcess(name string) (*PID, *mockProcess) {
+ p := &mockProcess{}
+
+ pid, ok := system.ProcessRegistry.Add(p, name)
+ if !ok {
+ panic(fmt.Errorf("did not spawn named process '%vids'", name))
+ }
+
+ return pid, p
+}
+
+func removeMockProcess(pid *PID) {
+ system.ProcessRegistry.Remove(pid)
+}
+
+func (m *mockProcess) SendUserMessage(pid *PID, message interface{}) {
+ m.Called(pid, message)
+}
+
+func (m *mockProcess) SendSystemMessage(pid *PID, message interface{}) {
+ m.Called(pid, message)
+}
+
+func (m *mockProcess) Stop(pid *PID) {
+ m.Called(pid)
+}
diff --git a/actor/config.go b/actor/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..6f54b6c477fe99a75b6de9aed530361cece7d5f7
--- /dev/null
+++ b/actor/config.go
@@ -0,0 +1,65 @@
+package actor
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/prometheus"
+ "go.opentelemetry.io/otel/metric"
+ sdkmetric "go.opentelemetry.io/otel/sdk/metric"
+
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+)
+
+type Config struct {
+ DeadLetterThrottleInterval time.Duration // throttle deadletter logging after this interval
+ DeadLetterThrottleCount int32 // throttle deadletter logging after this count
+ DeadLetterRequestLogging bool // do not log dead-letters with sender
+ DeveloperSupervisionLogging bool // console log and promote supervision logs to Warning level
+ DiagnosticsSerializer func(Actor) string // extract diagnostics from actor and return as string
+ MetricsProvider metric.MeterProvider
+}
+
+func defaultConfig() *Config {
+ return &Config{
+ MetricsProvider: nil,
+ DeadLetterThrottleInterval: 1 * time.Second,
+ DeadLetterThrottleCount: 3,
+ DeadLetterRequestLogging: true,
+ DeveloperSupervisionLogging: false,
+ DiagnosticsSerializer: func(actor Actor) string {
+ return ""
+ },
+ }
+}
+
+func defaultPrometheusProvider(port int) metric.MeterProvider {
+ exporter, err := prometheus.New()
+ if err != nil {
+ err = fmt.Errorf("failed to initialize prometheus exporter: %w", err)
+ plog.Error(err.Error(), log.Error(err))
+
+ return nil
+ }
+
+ provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(exporter.Reader))
+ otel.SetMeterProvider(provider)
+
+ http.Handle("/", promhttp.Handler())
+ _port := fmt.Sprintf(":%d", port)
+
+ go func() {
+ _ = http.ListenAndServe(_port, nil)
+ }()
+
+ plog.Debug(fmt.Sprintf("Prometheus server running on %s", _port))
+
+ return provider
+}
+
+func NewConfig() *Config {
+ return defaultConfig()
+}
diff --git a/actor/config_opts.go b/actor/config_opts.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebaec6f88594a33f755929b9682bb58d4a13ed96
--- /dev/null
+++ b/actor/config_opts.go
@@ -0,0 +1,63 @@
+package actor
+
+import (
+ "time"
+
+ "go.opentelemetry.io/otel/metric"
+)
+
+type ConfigOption func(config *Config)
+
+func Configure(options ...ConfigOption) *Config {
+ config := defaultConfig()
+ for _, option := range options {
+ option(config)
+ }
+
+ return config
+}
+
+func WithDeadLetterThrottleInterval(duration time.Duration) ConfigOption {
+ return func(config *Config) {
+ config.DeadLetterThrottleInterval = duration
+ }
+}
+
+func WithDeadLetterThrottleCount(count int32) ConfigOption {
+ return func(config *Config) {
+ config.DeadLetterThrottleCount = count
+ }
+}
+
+func WithDeadLetterRequestLogging(enabled bool) ConfigOption {
+ return func(config *Config) {
+ config.DeadLetterRequestLogging = enabled
+ }
+}
+
+func WithDeveloperSupervisionLogging(enabled bool) ConfigOption {
+ return func(config *Config) {
+ config.DeveloperSupervisionLogging = enabled
+ }
+}
+
+func WithDiagnosticsSerializer(serializer func(Actor) string) ConfigOption {
+ return func(config *Config) {
+ config.DiagnosticsSerializer = serializer
+ }
+}
+
+func WithMetricProviders(provider metric.MeterProvider) ConfigOption {
+ return func(config *Config) {
+ config.MetricsProvider = provider
+ }
+}
+
+func WithDefaultPrometheusProvider(port ...int) ConfigOption {
+ _port := 2222
+ if len(port) > 0 {
+ _port = port[0]
+ }
+
+ return WithMetricProviders(defaultPrometheusProvider(_port))
+}
diff --git a/actor/context.go b/actor/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..10f5a6dfca8af7f8515605f6fdae038f66fe55a2
--- /dev/null
+++ b/actor/context.go
@@ -0,0 +1,152 @@
+package actor
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/ctxext"
+)
+
+// Context contains contextual information for actors
+type Context interface {
+ infoPart
+ basePart
+ messagePart
+ senderPart
+ receiverPart
+ spawnerPart
+ stopperPart
+ extensionPart
+}
+
+type ExtensionContext interface {
+ extensionPart
+}
+
+type SenderContext interface {
+ infoPart
+ senderPart
+ messagePart
+}
+
+type ReceiverContext interface {
+ infoPart
+ receiverPart
+ messagePart
+ extensionPart
+}
+
+type SpawnerContext interface {
+ infoPart
+ spawnerPart
+}
+
+type extensionPart interface {
+ Get(id ctxext.ContextExtensionID) ctxext.ContextExtension
+ Set(ext ctxext.ContextExtension)
+}
+
+type infoPart interface {
+ // Parent returns the PID for the current actors parent
+ Parent() *PID
+
+ // Self returns the PID for the current actor
+ Self() *PID
+
+ // Actor returns the actor associated with this context
+ Actor() Actor
+
+ ActorSystem() *ActorSystem
+}
+
+type basePart interface {
+ // ReceiveTimeout returns the current timeout
+ ReceiveTimeout() time.Duration
+
+ // Children returns a slice of the actors children
+ Children() []*PID
+
+ // Respond sends a response to the current `Sender`
+ // If the Sender is nil, the actor will panic
+ Respond(response interface{})
+
+ // Stash stashes the current message on a stack for reprocessing when the actor restarts
+ Stash()
+
+ // Watch registers the actor as a monitor for the specified PID
+ Watch(pid *PID)
+
+ // Unwatch unregisters the actor as a monitor for the specified PID
+ Unwatch(pid *PID)
+
+ // SetReceiveTimeout sets the inactivity timeout, after which a ReceiveTimeout message will be sent to the actor.
+ // A duration of less than 1ms will disable the inactivity timer.
+ //
+ // If a message is received before the duration d, the timer will be reset. If the message conforms to
+ // the NotInfluenceReceiveTimeout interface, the timer will not be reset
+ SetReceiveTimeout(d time.Duration)
+
+ CancelReceiveTimeout()
+
+ // Forward forwards current message to the given PID
+ Forward(pid *PID)
+
+ ReenterAfter(f *Future, continuation func(res interface{}, err error))
+}
+
+type messagePart interface {
+ // Message returns the current message to be processed
+ Message() interface{}
+
+ // MessageHeader returns the meta information for the currently processed message
+ MessageHeader() ReadonlyMessageHeader
+}
+
+type senderPart interface {
+ // Sender returns the PID of actor that sent currently processed message
+ Sender() *PID
+
+ // Send sends a message to the given PID
+ Send(pid *PID, message interface{})
+
+ // Request sends a message to the given PID
+ Request(pid *PID, message interface{})
+
+ // RequestWithCustomSender sends a message to the given PID and also provides a Sender PID
+ RequestWithCustomSender(pid *PID, message interface{}, sender *PID)
+
+ // RequestFuture sends a message to a given PID and returns a Future
+ RequestFuture(pid *PID, message interface{}, timeout time.Duration) *Future
+}
+
+type receiverPart interface {
+ Receive(envelope *MessageEnvelope)
+}
+
+type spawnerPart interface {
+ // Spawn starts a new child actor based on props and named with a unique id
+ Spawn(props *Props) *PID
+
+ // SpawnPrefix starts a new child actor based on props and named using a prefix followed by a unique id
+ SpawnPrefix(props *Props, prefix string) *PID
+
+ // SpawnNamed starts a new child actor based on props and named using the specified name
+ //
+ // ErrNameExists will be returned if id already exists
+ //
+ // Please do not use name sharing same pattern with system actors, for example "YourPrefix$1", "Remote$1", "future$1"
+ SpawnNamed(props *Props, id string) (*PID, error)
+}
+
+type stopperPart interface {
+ // Stop will stop actor immediately regardless of existing user messages in mailbox.
+ Stop(pid *PID)
+
+ // StopFuture will stop actor immediately regardless of existing user messages in mailbox, and return its future.
+ StopFuture(pid *PID) *Future
+
+ // Poison will tell actor to stop after processing current user messages in mailbox.
+ Poison(pid *PID)
+
+ // PoisonFuture will tell actor to stop after processing current user messages in mailbox, and return its future.
+ PoisonFuture(pid *PID) *Future
+}
diff --git a/actor/context_example_setReceiveTimeout_test.go b/actor/context_example_setReceiveTimeout_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6cb27798d26a3ab7797c08d1afc34b2e4c948652
--- /dev/null
+++ b/actor/context_example_setReceiveTimeout_test.go
@@ -0,0 +1,44 @@
+package actor_test
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type setReceiveTimeoutActor struct {
+ *sync.WaitGroup
+}
+
+// Receive is the default message handler when an actor is started
+func (f *setReceiveTimeoutActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ case *actor.Started:
+ // when the actor starts, set the receive timeout to 10 milliseconds.
+ //
+ // the system will send an *actor.ReceiveTimeout message every time the timeout
+ // expires until SetReceiveTimeout is called again with a duration < 1 ms]
+ context.SetReceiveTimeout(10 * time.Millisecond)
+ case *actor.ReceiveTimeout:
+ fmt.Println("timed out")
+ f.Done()
+ }
+}
+
+// SetReceiveTimeout allows an actor to be notified repeatedly if it does not receive any messages
+// for a specified duration
+func ExampleContext_setReceiveTimeout() {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ pid := system.Root.Spawn(actor.PropsFromProducer(func() actor.Actor { return &setReceiveTimeoutActor{wg} }))
+ defer func() {
+ _ = system.Root.StopFuture(pid).Wait()
+ }()
+
+ wg.Wait() // wait for the ReceiveTimeout message
+
+ // Output: timed out
+}
diff --git a/actor/deadletter.go b/actor/deadletter.go
new file mode 100644
index 0000000000000000000000000000000000000000..63e79c570a79470993487cd02fb78d476cde951d
--- /dev/null
+++ b/actor/deadletter.go
@@ -0,0 +1,106 @@
+package actor
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/metrics"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+)
+
+type deadLetterProcess struct {
+ actorSystem *ActorSystem
+}
+
+var _ Process = &deadLetterProcess{}
+
+func NewDeadLetter(actorSystem *ActorSystem) *deadLetterProcess {
+ dp := &deadLetterProcess{
+ actorSystem: actorSystem,
+ }
+
+ shouldThrottle := NewThrottle(actorSystem.Config.DeadLetterThrottleCount, actorSystem.Config.DeadLetterThrottleInterval, func(i int32) {
+ plog.Info("[DeadLetter]", log.Int64("throttled", int64(i)))
+ })
+
+ actorSystem.ProcessRegistry.Add(dp, "deadletter")
+ _ = actorSystem.EventStream.Subscribe(func(msg interface{}) {
+ if deadLetter, ok := msg.(*DeadLetterEvent); ok {
+
+ // send back a response instead of timeout.
+ if deadLetter.Sender != nil {
+ actorSystem.Root.Send(deadLetter.Sender, &DeadLetterResponse{})
+ }
+
+ // bail out if sender is set and deadletter request logging is false
+ if !actorSystem.Config.DeadLetterRequestLogging && deadLetter.Sender != nil {
+ return
+ }
+
+ if _, isIgnoreDeadLetter := deadLetter.Message.(IgnoreDeadLetterLogging); !isIgnoreDeadLetter {
+ if shouldThrottle() == Open {
+ plog.Debug("[DeadLetter]", log.Stringer("pid", deadLetter.PID), log.TypeOf("msg", deadLetter.Message), log.Stringer("sender", deadLetter.Sender))
+ }
+ }
+ }
+ })
+
+ // this subscriber may not be deactivated.
+ // it ensures that Watch commands that reach a stopped actor gets a Terminated message back.
+ // This can happen if one actor tries to Watch a PID, while another thread sends a Stop message.
+ actorSystem.EventStream.Subscribe(func(msg interface{}) {
+ if deadLetter, ok := msg.(*DeadLetterEvent); ok {
+ if m, ok := deadLetter.Message.(*Watch); ok {
+ // we know that this is a local actor since we get it on our own event stream, thus the address is not terminated
+ m.Watcher.sendSystemMessage(actorSystem, &Terminated{
+ Who: deadLetter.PID,
+ Why: TerminatedReason_NotFound,
+ })
+ }
+ }
+ })
+
+ return dp
+}
+
+// A DeadLetterEvent is published via event.Publish when a message is sent to a nonexistent PID
+type DeadLetterEvent struct {
+ PID *PID // The invalid process, to which the message was sent
+ Message interface{} // The message that could not be delivered
+ Sender *PID // the process that sent the Message
+}
+
+func (dp *deadLetterProcess) SendUserMessage(pid *PID, message interface{}) {
+ metricsSystem, ok := dp.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && metricsSystem.enabled {
+ ctx := context.Background()
+ if instruments := metricsSystem.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ labels := []attribute.KeyValue{
+ attribute.String("address", dp.actorSystem.Address()),
+ attribute.String("messagetype", strings.Replace(fmt.Sprintf("%T", message), "*", "", 1)),
+ }
+
+ instruments.DeadLetterCount.Add(ctx, 1, metric.WithAttributes(labels...))
+ }
+ }
+ _, msg, sender := UnwrapEnvelope(message)
+ dp.actorSystem.EventStream.Publish(&DeadLetterEvent{
+ PID: pid,
+ Message: msg,
+ Sender: sender,
+ })
+}
+
+func (dp *deadLetterProcess) SendSystemMessage(pid *PID, message interface{}) {
+ dp.actorSystem.EventStream.Publish(&DeadLetterEvent{
+ PID: pid,
+ Message: message,
+ })
+}
+
+func (dp *deadLetterProcess) Stop(pid *PID) {
+ dp.SendSystemMessage(pid, stopMessage)
+}
diff --git a/actor/deadletter_test.go b/actor/deadletter_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..31347e41b609b13add1e4a1e93ef1e23f9bd87f0
--- /dev/null
+++ b/actor/deadletter_test.go
@@ -0,0 +1,37 @@
+package actor
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDeadLetterAfterStop(t *testing.T) {
+ a := rootContext.Spawn(PropsFromProducer(NewBlackHoleActor))
+ done := false
+ sub := system.EventStream.Subscribe(func(msg interface{}) {
+ if deadLetter, ok := msg.(*DeadLetterEvent); ok {
+ if deadLetter.PID == a {
+ done = true
+ }
+ }
+ })
+ defer system.EventStream.Unsubscribe(sub)
+
+ _ = rootContext.StopFuture(a).Wait()
+
+ rootContext.Send(a, "hello")
+
+ assert.True(t, done)
+}
+
+func TestDeadLetterWatchRespondsWithTerminate(t *testing.T) {
+ // create an actor
+ pid := rootContext.Spawn(PropsFromProducer(NewBlackHoleActor))
+ // stop id
+ _ = rootContext.StopFuture(pid).Wait()
+ f := NewFuture(system, testTimeout)
+ // send a watch message, from our future
+ pid.sendSystemMessage(system, &Watch{Watcher: f.PID()})
+ assertFutureSuccess(f, t)
+}
diff --git a/actor/directive.go b/actor/directive.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2ae5d11e66d7d4931ad3504851f9be344fbdd7a
--- /dev/null
+++ b/actor/directive.go
@@ -0,0 +1,20 @@
+package actor
+
+// Directive is an enum for supervision actions
+type Directive int
+
+// Directive determines how a supervisor should handle a faulting actor
+const (
+ // ResumeDirective instructs the supervisor to resume the actor and continue processing messages
+ ResumeDirective Directive = iota
+
+ // RestartDirective instructs the supervisor to discard the actor, replacing it with a new instance,
+ // before processing additional messages
+ RestartDirective
+
+ // StopDirective instructs the supervisor to stop the actor
+ StopDirective
+
+ // EscalateDirective instructs the supervisor to escalate handling of the failure to the actor'pids parent supervisor
+ EscalateDirective
+)
diff --git a/actor/directive_string.go b/actor/directive_string.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7c2c0cc1e68e0772c7c457fd1aae9d02d18a8a3
--- /dev/null
+++ b/actor/directive_string.go
@@ -0,0 +1,18 @@
+// Code generated by "stringer -type=Directive"; DO NOT EDIT
+
+package actor
+
+import "fmt"
+
+//goland:noinspection GoSnakeCaseUsage
+const _Directive_name = "ResumeDirectiveRestartDirectiveStopDirectiveEscalateDirective"
+
+//goland:noinspection GoSnakeCaseUsage
+var _Directive_index = [...]uint8{0, 15, 31, 44, 61}
+
+func (i Directive) String() string {
+ if i < 0 || i >= Directive(len(_Directive_index)-1) {
+ return fmt.Sprintf("Directive(%d)", i)
+ }
+ return _Directive_name[_Directive_index[i]:_Directive_index[i+1]]
+}
diff --git a/actor/dispatcher.go b/actor/dispatcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..b856db1498d8251fcb298645580b095ef737916c
--- /dev/null
+++ b/actor/dispatcher.go
@@ -0,0 +1,38 @@
+package actor
+
+type Dispatcher interface {
+ Schedule(fn func())
+ Throughput() int
+}
+
+type goroutineDispatcher int
+
+var _ Dispatcher = goroutineDispatcher(0)
+
+func (goroutineDispatcher) Schedule(fn func()) {
+ go fn()
+}
+
+func (d goroutineDispatcher) Throughput() int {
+ return int(d)
+}
+
+func NewDefaultDispatcher(throughput int) Dispatcher {
+ return goroutineDispatcher(throughput)
+}
+
+type synchronizedDispatcher int
+
+var _ Dispatcher = synchronizedDispatcher(0)
+
+func (synchronizedDispatcher) Schedule(fn func()) {
+ fn()
+}
+
+func (d synchronizedDispatcher) Throughput() int {
+ return int(d)
+}
+
+func NewSynchronizedDispatcher(throughput int) Dispatcher {
+ return synchronizedDispatcher(throughput)
+}
diff --git a/actor/doc.go b/actor/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..c70ec420c3dc9fe3676f6b003cbefb2c9f998624
--- /dev/null
+++ b/actor/doc.go
@@ -0,0 +1,70 @@
+/*
+Package actor declares the types used to represent actors in the Actor Model.
+
+The actors model provide a high level abstraction for writing concurrent and distributed systems. This approach
+simplifies the burden imposed on engineers, such as explicit locks and concurrent access to shared state, as actors
+receive messages synchronously.
+
+The following quote from Wikipedia distills the definition of an actor down to its essence
+
+ In response to a message that it receives, an actor can: make local decisions, create more actors,
+ send more messages, and determine how to respond to the next message received.
+
+# Creating Actors
+
+Props provide the building blocks for declaring how actors should be created. The following example defines an actor
+using a function literal to process messages:
+
+ var props Props = actor.PropsFromFunc(func(c Context) {
+ // process messages
+ })
+
+Alternatively, a type which conforms to the Actor interface, by defining a single Receive method, can be used.
+
+ type MyActor struct {}
+
+ func (a *MyActor) Receive(c Context) {
+ // process messages
+ }
+
+ var props Props = actor.PropsFromProducer(func() Actor { return &MyActor{} })
+
+Spawn and SpawnNamed use the given props to create a running instances of an actor. Once spawned, the actor is
+ready to process incoming messages. To spawn an actor with a unique name, use
+
+ pid := context.Spawn(props)
+
+The result of calling Spawn is a unique PID or process identifier.
+
+Each time an actor is spawned, a new mailbox is created and associated with the PID. Messages are sent to the mailbox
+and then forwarded to the actor to process.
+
+# Processing Messages
+
+An actor processes messages via its Receive handler. The signature of this function is:
+
+ Receive(c actor.Context)
+
+The actor system guarantees that this method is called synchronously, therefore there is no requirement to protect
+shared state inside calls to this function.
+
+# Communicating With Actors
+
+A PID is the primary interface for sending messages to actors. Context.Send is used to send an asynchronous
+message to the actor associated with the PID:
+
+ context.Aend(pid, "Hello World")
+
+Depending on the requirements, communication between actors can take place synchronously or asynchronously. Regardless
+of the circumstances, actors always communicate via a PID.
+
+When sending a message using PID.Request or PID.RequestFuture, the actor which receives the message will respond
+using the Context.Sender method, which returns the PID of of the sender.
+
+For synchronous communication, an actor will use a Future and wait for the result before continuing. To send a message
+to an actor and wait for a response, use the RequestFuture method, which returns a Future:
+
+ f := actor.RequestFuture(pid,"Hello", 50 * time.Millisecond)
+ res, err := f.Result() // waits for pid to reply
+*/
+package actor
diff --git a/actor/eventstream_process.go b/actor/eventstream_process.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a7c4c3826adce8e4a9f7bd2fce265c773e9afd2
--- /dev/null
+++ b/actor/eventstream_process.go
@@ -0,0 +1,24 @@
+package actor
+
+type EventStreamProcess struct {
+ system *ActorSystem
+}
+
+var _ Process = &EventStreamProcess{}
+
+func NewEventStreamProcess(actorSystem *ActorSystem) *EventStreamProcess {
+ return &EventStreamProcess{system: actorSystem}
+}
+
+func (e *EventStreamProcess) SendUserMessage(_ *PID, message interface{}) {
+ _, msg, _ := UnwrapEnvelope(message)
+ e.system.EventStream.Publish(msg)
+}
+
+func (e *EventStreamProcess) SendSystemMessage(_ *PID, _ interface{}) {
+ // pass
+}
+
+func (e *EventStreamProcess) Stop(_ *PID) {
+ // pass
+}
diff --git a/actor/eventstream_process_test.go b/actor/eventstream_process_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b871910d85ccec4c5b429f365e2d06517a6ebc4
--- /dev/null
+++ b/actor/eventstream_process_test.go
@@ -0,0 +1,38 @@
+package actor
+
+import (
+ "testing"
+)
+
+type EsTestMsg struct{}
+
+func TestSendsMessagesToEventStream(t *testing.T) {
+ testCases := []struct {
+ name string
+ message interface{}
+ }{
+ {name: "plain", message: &EsTestMsg{}},
+ {name: "envelope", message: WrapEnvelope(&EsTestMsg{})},
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ system := NewActorSystem()
+
+ gotMessageChan := make(chan struct{}, 1)
+
+ subscription := system.EventStream.Subscribe(func(evt interface{}) {
+ if _, ok := evt.(*EsTestMsg); ok {
+ gotMessageChan <- struct{}{}
+ }
+ })
+ defer system.EventStream.Unsubscribe(subscription)
+
+ pid := system.NewLocalPID("eventstream")
+
+ system.Root.Send(pid, testCase.message)
+
+ <-gotMessageChan
+ })
+ }
+}
diff --git a/actor/future.go b/actor/future.go
new file mode 100644
index 0000000000000000000000000000000000000000..a3c55bff0d7e98133f302a8a8616a4cd93c15be5
--- /dev/null
+++ b/actor/future.go
@@ -0,0 +1,228 @@
+package actor
+
+import (
+ "context"
+ "errors"
+ "sync"
+ "sync/atomic"
+ "time"
+ "unsafe"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/metrics"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+)
+
+// ErrTimeout is the error used when a future times out before receiving a result.
+var ErrTimeout = errors.New("future: timeout")
+
+// ErrDeadLetter is meaning you request to a unreachable PID.
+var ErrDeadLetter = errors.New("future: dead letter")
+
+// NewFuture creates and returns a new actor.Future with a timeout of duration d.
+func NewFuture(actorSystem *ActorSystem, d time.Duration) *Future {
+ ref := &futureProcess{Future{actorSystem: actorSystem, cond: sync.NewCond(&sync.Mutex{})}}
+ id := actorSystem.ProcessRegistry.NextId()
+
+ pid, ok := actorSystem.ProcessRegistry.Add(ref, "future"+id)
+ if !ok {
+ plog.Error("failed to register future process", log.Stringer("pid", pid))
+ }
+
+ sysMetrics, ok := actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && sysMetrics.enabled {
+ if instruments := sysMetrics.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ ctx := context.Background()
+ labels := []attribute.KeyValue{
+ attribute.String("address", ref.actorSystem.Address()),
+ }
+
+ instruments.FuturesStartedCount.Add(ctx, 1, metric.WithAttributes(labels...))
+ }
+ }
+
+ ref.pid = pid
+
+ if d >= 0 {
+ tp := time.AfterFunc(d, func() {
+ ref.cond.L.Lock()
+ if ref.done {
+ ref.cond.L.Unlock()
+
+ return
+ }
+ ref.err = ErrTimeout
+ ref.cond.L.Unlock()
+ ref.Stop(pid)
+ })
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&ref.t)), unsafe.Pointer(tp))
+ }
+
+ return &ref.Future
+}
+
+type Future struct {
+ actorSystem *ActorSystem
+ pid *PID
+ cond *sync.Cond
+ // protected by cond
+ done bool
+ result interface{}
+ err error
+ t *time.Timer
+ pipes []*PID
+ completions []func(res interface{}, err error)
+}
+
+// PID to the backing actor for the Future result.
+func (f *Future) PID() *PID {
+ return f.pid
+}
+
+// PipeTo forwards the result or error of the future to the specified pids.
+func (f *Future) PipeTo(pids ...*PID) {
+ f.cond.L.Lock()
+ f.pipes = append(f.pipes, pids...)
+ // for an already completed future, force push the result to targets.
+ if f.done {
+ f.sendToPipes()
+ }
+ f.cond.L.Unlock()
+}
+
+func (f *Future) sendToPipes() {
+ if f.pipes == nil {
+ return
+ }
+
+ var m interface{}
+ if f.err != nil {
+ m = f.err
+ } else {
+ m = f.result
+ }
+
+ for _, pid := range f.pipes {
+ pid.sendUserMessage(f.actorSystem, m)
+ }
+
+ f.pipes = nil
+}
+
+func (f *Future) wait() {
+ f.cond.L.Lock()
+ for !f.done {
+ f.cond.Wait()
+ }
+ f.cond.L.Unlock()
+}
+
+// Result waits for the future to resolve.
+func (f *Future) Result() (interface{}, error) {
+ f.wait()
+
+ return f.result, f.err
+}
+
+func (f *Future) Wait() error {
+ f.wait()
+
+ return f.err
+}
+
+func (f *Future) continueWith(continuation func(res interface{}, err error)) {
+ f.cond.L.Lock()
+ defer f.cond.L.Unlock() // use defer as the continuation co
+ // uld blow up
+ if f.done {
+ continuation(f.result, f.err)
+ } else {
+ f.completions = append(f.completions, continuation)
+ }
+}
+
+// futureProcess is a struct carrying a response PID and a channel where the response is placed.
+type futureProcess struct {
+ Future
+}
+
+var _ Process = &futureProcess{}
+
+func (ref *futureProcess) SendUserMessage(pid *PID, message interface{}) {
+ defer ref.instrument()
+
+ _, msg, _ := UnwrapEnvelope(message)
+
+ if _, ok := msg.(*DeadLetterResponse); ok {
+ ref.result = nil
+ ref.err = ErrDeadLetter
+ } else {
+ ref.result = msg
+ }
+
+ ref.Stop(pid)
+}
+
+func (ref *futureProcess) SendSystemMessage(pid *PID, message interface{}) {
+ defer ref.instrument()
+ ref.result = message
+ ref.Stop(pid)
+}
+
+func (ref *futureProcess) instrument() {
+ sysMetrics, ok := ref.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && sysMetrics.enabled {
+ ctx := context.Background()
+ labels := []attribute.KeyValue{
+ attribute.String("address", ref.actorSystem.Address()),
+ }
+
+ instruments := sysMetrics.metrics.Get(metrics.InternalActorMetrics)
+ if instruments != nil {
+ if ref.err == nil {
+ instruments.FuturesCompletedCount.Add(ctx, 1, metric.WithAttributes(labels...))
+ } else {
+ instruments.FuturesTimedOutCount.Add(ctx, 1, metric.WithAttributes(labels...))
+ }
+ }
+ }
+}
+
+func (ref *futureProcess) Stop(pid *PID) {
+ ref.cond.L.Lock()
+ if ref.done {
+ ref.cond.L.Unlock()
+
+ return
+ }
+
+ ref.done = true
+ tp := (*time.Timer)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&ref.t))))
+
+ if tp != nil {
+ tp.Stop()
+ }
+
+ ref.actorSystem.ProcessRegistry.Remove(pid)
+
+ ref.sendToPipes()
+ ref.runCompletions()
+ ref.cond.L.Unlock()
+ ref.cond.Signal()
+}
+
+// TODO: we could replace "pipes" with this
+// instead of pushing PIDs to pipes, we could push wrapper funcs that tells the pid
+// as a completion, that would unify the model.
+func (f *Future) runCompletions() {
+ if f.completions == nil {
+ return
+ }
+
+ for _, c := range f.completions {
+ c(f.result, f.err)
+ }
+
+ f.completions = nil
+}
diff --git a/actor/future_example_test.go b/actor/future_example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c512e9fa573b3d2a744f919ac23e69812d16a102
--- /dev/null
+++ b/actor/future_example_test.go
@@ -0,0 +1,34 @@
+package actor_test
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+var system = actor.NewActorSystem()
+
+func ExampleFuture_PipeTo() {
+ var wg sync.WaitGroup
+ wg.Add(1)
+
+ // test actor that will be the target of the future PipeTo
+ pid := system.Root.Spawn(actor.PropsFromFunc(func(ctx actor.Context) {
+ // check if the message is a string and therefore
+ // the "hello world" message piped from the future
+ if m, ok := ctx.Message().(string); ok {
+ fmt.Println(m)
+ wg.Done()
+ }
+ }))
+
+ f := actor.NewFuture(system, 50*time.Millisecond)
+ f.PipeTo(pid)
+ // resolve the future and pipe to waiting actor
+ system.Root.Send(f.PID(), "hello world")
+ wg.Wait()
+
+ // Output: hello world
+}
diff --git a/actor/future_test.go b/actor/future_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bf860a1aff712f7fbedbda4b36407ff33a2c5846
--- /dev/null
+++ b/actor/future_test.go
@@ -0,0 +1,126 @@
+package actor
+
+import (
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFuture_PipeTo_Message(t *testing.T) {
+ a1, p1 := spawnMockProcess("a1")
+ a2, p2 := spawnMockProcess("a2")
+ a3, p3 := spawnMockProcess("a3")
+ defer func() {
+ removeMockProcess(a1)
+ removeMockProcess(a2)
+ removeMockProcess(a3)
+ }()
+
+ f := NewFuture(system, 1*time.Second)
+
+ p1.On("SendUserMessage", a1, "hello")
+ p2.On("SendUserMessage", a2, "hello")
+ p3.On("SendUserMessage", a3, "hello")
+
+ f.PipeTo(a1)
+ f.PipeTo(a2)
+ f.PipeTo(a3)
+
+ ref, _ := system.ProcessRegistry.Get(f.pid)
+ assert.IsType(t, &futureProcess{}, ref)
+ fp, _ := ref.(*futureProcess)
+
+ fp.SendUserMessage(f.pid, "hello")
+ p1.AssertExpectations(t)
+ p2.AssertExpectations(t)
+ p3.AssertExpectations(t)
+ assert.Empty(t, fp.pipes, "pipes were not cleared")
+}
+
+func TestFuture_PipeTo_TimeoutSendsError(t *testing.T) {
+ a1, p1 := spawnMockProcess("a1")
+ a2, p2 := spawnMockProcess("a2")
+ a3, p3 := spawnMockProcess("a3")
+ defer func() {
+ removeMockProcess(a1)
+ removeMockProcess(a2)
+ removeMockProcess(a3)
+ }()
+
+ p1.On("SendUserMessage", a1, ErrTimeout)
+ p2.On("SendUserMessage", a2, ErrTimeout)
+ p3.On("SendUserMessage", a3, ErrTimeout)
+
+ f := NewFuture(system, 10*time.Millisecond)
+ ref, _ := system.ProcessRegistry.Get(f.pid)
+
+ f.PipeTo(a1)
+ f.PipeTo(a2)
+ f.PipeTo(a3)
+
+ err := f.Wait()
+ assert.Error(t, err)
+
+ assert.IsType(t, &futureProcess{}, ref)
+ fp, _ := ref.(*futureProcess)
+
+ p1.AssertExpectations(t)
+ p2.AssertExpectations(t)
+ p3.AssertExpectations(t)
+ assert.Empty(t, fp.pipes, "pipes were not cleared")
+}
+
+func TestNewFuture_TimeoutNoRace(t *testing.T) {
+ plog.SetLevel(log.OffLevel)
+ future := NewFuture(system, 1*time.Microsecond)
+ a := rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *Started:
+ context.Send(future.PID(), EchoResponse{})
+ }
+ }))
+ _ = rootContext.StopFuture(a).Wait()
+ _, _ = future.Result()
+}
+
+func assertFutureSuccess(future *Future, t *testing.T) interface{} {
+ res, err := future.Result()
+ assert.NoError(t, err, "timed out")
+ return res
+}
+
+func TestFuture_Result_DeadLetterResponse(t *testing.T) {
+ a := assert.New(t)
+
+ plog.SetLevel(log.OffLevel)
+
+ future := NewFuture(system, 1*time.Second)
+ rootContext.Send(future.PID(), &DeadLetterResponse{})
+ resp, err := future.Result()
+ a.Equal(ErrDeadLetter, err)
+ a.Nil(resp)
+}
+
+func TestFuture_Result_Timeout(t *testing.T) {
+ a := assert.New(t)
+
+ plog.SetLevel(log.OffLevel)
+
+ future := NewFuture(system, 1*time.Second)
+ resp, err := future.Result()
+ a.Equal(ErrTimeout, err)
+ a.Nil(resp)
+}
+
+func TestFuture_Result_Success(t *testing.T) {
+ a := assert.New(t)
+
+ plog.SetLevel(log.OffLevel)
+
+ future := NewFuture(system, 1*time.Second)
+ rootContext.Send(future.PID(), EchoResponse{})
+ resp := assertFutureSuccess(future, t)
+ a.Equal(EchoResponse{}, resp)
+}
diff --git a/actor/guardian.go b/actor/guardian.go
new file mode 100644
index 0000000000000000000000000000000000000000..c730f2e5d8f82ebfadcb5cc913be53e10271cb5b
--- /dev/null
+++ b/actor/guardian.go
@@ -0,0 +1,94 @@
+package actor
+
+import (
+ "errors"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+type guardiansValue struct {
+ actorSystem *ActorSystem
+ guardians *sync.Map
+}
+
+func NewGuardians(actorSystem *ActorSystem) *guardiansValue {
+ return &guardiansValue{
+ actorSystem: actorSystem,
+ guardians: &sync.Map{},
+ }
+}
+
+func (gs *guardiansValue) getGuardianPid(s SupervisorStrategy) *PID {
+ if g, ok := gs.guardians.Load(s); ok {
+ return g.(*guardianProcess).pid
+ }
+ g := gs.newGuardian(s)
+ gs.guardians.Store(s, g)
+ return g.pid
+}
+
+// newGuardian creates and returns a new actor.guardianProcess with a timeout of duration d
+func (gs *guardiansValue) newGuardian(s SupervisorStrategy) *guardianProcess {
+ ref := &guardianProcess{
+ strategy: s,
+ guardians: gs,
+ }
+ id := gs.actorSystem.ProcessRegistry.NextId()
+
+ pid, ok := gs.actorSystem.ProcessRegistry.Add(ref, "guardian"+id)
+ if !ok {
+ plog.Error("failed to register guardian process", log.Stringer("pid", pid))
+ }
+
+ ref.pid = pid
+ return ref
+}
+
+type guardianProcess struct {
+ guardians *guardiansValue
+ pid *PID
+ strategy SupervisorStrategy
+}
+
+var _ Process = &guardianProcess{}
+
+func (g *guardianProcess) SendUserMessage(_ *PID, _ interface{}) {
+ panic(errors.New("guardian actor cannot receive any user messages"))
+}
+
+func (g *guardianProcess) SendSystemMessage(_ *PID, message interface{}) {
+ if msg, ok := message.(*Failure); ok {
+ g.strategy.HandleFailure(g.guardians.actorSystem, g, msg.Who, msg.RestartStats, msg.Reason, msg.Message)
+ }
+}
+
+func (g *guardianProcess) Stop(_ *PID) {
+ // Ignore
+}
+
+func (g *guardianProcess) Children() []*PID {
+ panic(errors.New("guardian does not hold its children PIDs"))
+}
+
+func (g *guardianProcess) EscalateFailure(_ interface{}, _ interface{}) {
+ panic(errors.New("guardian cannot escalate failure"))
+}
+
+func (g *guardianProcess) RestartChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(g.guardians.actorSystem, restartMessage)
+ }
+}
+
+func (g *guardianProcess) StopChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(g.guardians.actorSystem, stopMessage)
+ }
+}
+
+func (g *guardianProcess) ResumeChildren(pids ...*PID) {
+ for _, pid := range pids {
+ pid.sendSystemMessage(g.guardians.actorSystem, resumeMailboxMessage)
+ }
+}
diff --git a/actor/interaction_test.go b/actor/interaction_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..49a2d5bf0b59a8fdf39da38511906ba016a6a6d0
--- /dev/null
+++ b/actor/interaction_test.go
@@ -0,0 +1,54 @@
+package actor
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type (
+ DummyMessage struct{}
+ BlackHoleActor struct{}
+)
+
+var testTimeout = 1 * time.Second
+
+func (state *BlackHoleActor) Receive(Context) {}
+
+func NewBlackHoleActor() Actor {
+ return &BlackHoleActor{}
+}
+
+func TestSpawnProducesProcess(t *testing.T) {
+ actor := rootContext.Spawn(PropsFromProducer(NewBlackHoleActor))
+ defer rootContext.Stop(actor)
+ assert.NotNil(t, actor)
+}
+
+type EchoRequest struct{}
+
+type EchoResponse struct{}
+
+type EchoActor struct{}
+
+func NewEchoActor() Actor {
+ return &EchoActor{}
+}
+
+func (*EchoActor) Receive(context Context) {
+ switch context.Message().(type) {
+ case EchoRequest:
+ context.Respond(EchoResponse{})
+ }
+}
+
+func TestActorCanReplyToMessage(t *testing.T) {
+ pid := rootContext.Spawn(PropsFromProducer(NewEchoActor))
+ defer rootContext.Stop(pid)
+ err := rootContext.RequestFuture(pid, EchoRequest{}, testTimeout).Wait()
+ if err != nil {
+ assert.Fail(t, "timed out")
+ return
+ }
+}
diff --git a/actor/lifecycle_test.go b/actor/lifecycle_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ef36cb941e38cf7faa1f4bd806d6d942df8c116
--- /dev/null
+++ b/actor/lifecycle_test.go
@@ -0,0 +1,72 @@
+package actor
+
+import (
+ "testing"
+)
+
+type (
+ dummyRequest struct{}
+ dummyResponse struct{}
+)
+
+func TestActorCanReplyOnStarting(t *testing.T) {
+ future := NewFuture(system, testTimeout)
+ a := rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *Started:
+ context.Send(future.PID(), dummyResponse{})
+ }
+ }))
+ _ = rootContext.StopFuture(a).Wait()
+ assertFutureSuccess(future, t)
+}
+
+func TestActorCanReplyOnStopping(t *testing.T) {
+ future := NewFuture(system, testTimeout)
+ a := rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *Stopping:
+ context.Send(future.PID(), dummyResponse{})
+ }
+ }))
+ _ = rootContext.StopFuture(a).Wait()
+ assertFutureSuccess(future, t)
+}
+
+func TestActorReceivesStartedMessage(t *testing.T) {
+ future := NewFuture(system, testTimeout)
+ _ = rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *Started:
+ context.Send(future.PID(), dummyResponse{})
+ }
+ }))
+ _ = future.Wait()
+ assertFutureSuccess(future, t)
+}
+
+func TestActorReceivesRestartingMessage(t *testing.T) {
+ future := NewFuture(system, testTimeout)
+ a := rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *dummyRequest:
+ panic("fail")
+ case *Restarting:
+ context.Send(future.PID(), dummyResponse{})
+ }
+ }))
+ rootContext.Send(a, &dummyRequest{})
+ assertFutureSuccess(future, t)
+}
+
+func TestActorReceivesStoppingMessage(t *testing.T) {
+ future := NewFuture(system, testTimeout)
+ a := rootContext.Spawn(PropsFromFunc(func(context Context) {
+ switch context.Message().(type) {
+ case *Stopping:
+ context.Send(future.PID(), dummyResponse{})
+ }
+ }))
+ _ = rootContext.StopFuture(a).Wait()
+ assertFutureSuccess(future, t)
+}
diff --git a/actor/log.go b/actor/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..6114c20afce43bf28a074d1e0953a50fbb9cb811
--- /dev/null
+++ b/actor/log.go
@@ -0,0 +1,14 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+var plog = log.New(log.DebugLevel, "[ACTOR]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/actor/mailbox.go b/actor/mailbox.go
new file mode 100644
index 0000000000000000000000000000000000000000..5e5d936bf3b87d999b9d08570345b2484d51497a
--- /dev/null
+++ b/actor/mailbox.go
@@ -0,0 +1,198 @@
+package actor
+
+import (
+ "runtime"
+ "sync/atomic"
+
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+// MailboxMiddleware is an interface for intercepting messages and events in the mailbox
+type MailboxMiddleware interface {
+ MailboxStarted()
+ MessagePosted(message interface{})
+ MessageReceived(message interface{})
+ MailboxEmpty()
+}
+
+// MessageInvoker is the interface used by a mailbox to forward messages for processing
+type MessageInvoker interface {
+ InvokeSystemMessage(interface{})
+ InvokeUserMessage(interface{})
+ EscalateFailure(reason interface{}, message interface{})
+}
+
+// Mailbox interface is used to enqueue messages to the mailbox
+type Mailbox interface {
+ PostUserMessage(message interface{})
+ PostSystemMessage(message interface{})
+ RegisterHandlers(invoker MessageInvoker, dispatcher Dispatcher)
+ Start()
+ UserMessageCount() int
+}
+
+// MailboxProducer is a function which creates a new mailbox
+type MailboxProducer func() Mailbox
+
+const (
+ idle int32 = iota
+ running
+)
+
+type defaultMailbox struct {
+ userMailbox queue
+ systemMailbox *mpsc.Queue
+ schedulerStatus int32
+ userMessages int32
+ sysMessages int32
+ suspended int32
+ invoker MessageInvoker
+ dispatcher Dispatcher
+ middlewares []MailboxMiddleware
+}
+
+func (m *defaultMailbox) PostUserMessage(message interface{}) {
+ // is it a raw batch message?
+ if batch, ok := message.(MessageBatch); ok {
+ messages := batch.GetMessages()
+
+ for _, msg := range messages {
+ m.PostUserMessage(msg)
+ }
+ }
+
+ // is it an envelope batch message?
+ // FIXME: check if this is still needed, maybe MessageEnvelope can only exist as a pointer
+ if env, ok := message.(MessageEnvelope); ok {
+ if batch, ok := env.Message.(MessageBatch); ok {
+ messages := batch.GetMessages()
+
+ for _, msg := range messages {
+ m.PostUserMessage(msg)
+ }
+ }
+ }
+ if env, ok := message.(*MessageEnvelope); ok {
+ if batch, ok := env.Message.(MessageBatch); ok {
+ messages := batch.GetMessages()
+
+ for _, msg := range messages {
+ m.PostUserMessage(msg)
+ }
+ }
+ }
+
+ // normal messages
+ for _, ms := range m.middlewares {
+ ms.MessagePosted(message)
+ }
+ m.userMailbox.Push(message)
+ atomic.AddInt32(&m.userMessages, 1)
+ m.schedule()
+}
+
+func (m *defaultMailbox) PostSystemMessage(message interface{}) {
+ for _, ms := range m.middlewares {
+ ms.MessagePosted(message)
+ }
+ m.systemMailbox.Push(message)
+ atomic.AddInt32(&m.sysMessages, 1)
+ m.schedule()
+}
+
+func (m *defaultMailbox) RegisterHandlers(invoker MessageInvoker, dispatcher Dispatcher) {
+ m.invoker = invoker
+ m.dispatcher = dispatcher
+}
+
+func (m *defaultMailbox) schedule() {
+ if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
+ m.dispatcher.Schedule(m.processMessages)
+ }
+}
+
+func (m *defaultMailbox) processMessages() {
+process:
+ m.run()
+
+ // set mailbox to idle
+ atomic.StoreInt32(&m.schedulerStatus, idle)
+ sys := atomic.LoadInt32(&m.sysMessages)
+ user := atomic.LoadInt32(&m.userMessages)
+ // check if there are still messages to process (sent after the message loop ended)
+ if sys > 0 || (atomic.LoadInt32(&m.suspended) == 0 && user > 0) {
+ // try setting the mailbox back to running
+ if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
+ // fmt.Printf("looping %v %v %v\n", sys, user, m.suspended)
+ goto process
+ }
+ }
+
+ for _, ms := range m.middlewares {
+ ms.MailboxEmpty()
+ }
+}
+
+func (m *defaultMailbox) run() {
+ var msg interface{}
+
+ defer func() {
+ if r := recover(); r != nil {
+ plog.Info("[ACTOR] Recovering", log.Object("actor", m.invoker), log.Object("reason", r), log.Stack())
+ m.invoker.EscalateFailure(r, msg)
+ }
+ }()
+
+ i, t := 0, m.dispatcher.Throughput()
+ for {
+ if i > t {
+ i = 0
+ runtime.Gosched()
+ }
+
+ i++
+
+ // keep processing system messages until queue is empty
+ if msg = m.systemMailbox.Pop(); msg != nil {
+ atomic.AddInt32(&m.sysMessages, -1)
+ switch msg.(type) {
+ case *SuspendMailbox:
+ atomic.StoreInt32(&m.suspended, 1)
+ case *ResumeMailbox:
+ atomic.StoreInt32(&m.suspended, 0)
+ default:
+ m.invoker.InvokeSystemMessage(msg)
+ }
+ for _, ms := range m.middlewares {
+ ms.MessageReceived(msg)
+ }
+ continue
+ }
+
+ // didn't process a system message, so break until we are resumed
+ if atomic.LoadInt32(&m.suspended) == 1 {
+ return
+ }
+
+ if msg = m.userMailbox.Pop(); msg != nil {
+ atomic.AddInt32(&m.userMessages, -1)
+ m.invoker.InvokeUserMessage(msg)
+ for _, ms := range m.middlewares {
+ ms.MessageReceived(msg)
+ }
+ } else {
+ return
+ }
+ }
+}
+
+func (m *defaultMailbox) Start() {
+ for _, ms := range m.middlewares {
+ ms.MailboxStarted()
+ }
+}
+
+func (m *defaultMailbox) UserMessageCount() int {
+ return int(atomic.LoadInt32(&m.userMessages))
+}
diff --git a/actor/mailbox_test.go b/actor/mailbox_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5780ca2218dec5a7b692d579916970f2486e22cf
--- /dev/null
+++ b/actor/mailbox_test.go
@@ -0,0 +1,154 @@
+package actor
+
+import (
+ "fmt"
+ "log"
+ "math/rand"
+ "sync"
+ "testing"
+ "time"
+
+ rbqueue "github.com/Workiva/go-datastructures/queue"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type invoker struct {
+ count int
+ max int
+ wg *sync.WaitGroup
+}
+
+func (i *invoker) InvokeSystemMessage(interface{}) {
+ i.count++
+ if i.count == i.max {
+ i.wg.Done()
+ }
+ if i.count > i.max {
+ log.Println("Unexpected data..")
+ }
+}
+
+func (i *invoker) InvokeUserMessage(interface{}) {
+ i.count++
+ if i.count == i.max {
+ i.wg.Done()
+ }
+ if i.count > i.max {
+ log.Println("Unexpected data..")
+ }
+}
+
+func (*invoker) EscalateFailure(_ interface{}, _ interface{}) {}
+
+func TestUnboundedLockfreeMailboxUsermessageConsistency(t *testing.T) {
+ max := 1000000
+ c := 100
+ var wg sync.WaitGroup
+ wg.Add(1)
+ p := UnboundedLockfree()
+ mi := &invoker{
+ max: max,
+ wg: &wg,
+ }
+ q := p()
+ q.RegisterHandlers(mi, NewDefaultDispatcher(300))
+
+ for j := 0; j < c; j++ {
+ cmax := max / c
+ go func(j int) {
+ for i := 0; i < cmax; i++ {
+ if rand.Intn(10) == 0 {
+ time.Sleep(time.Duration(rand.Intn(1000)))
+ }
+ q.PostUserMessage(fmt.Sprintf("%v %v", j, i))
+ }
+ }(j)
+ }
+ wg.Wait()
+ time.Sleep(1 * time.Second)
+}
+
+type sysDummy struct {
+ value string
+}
+
+func (*sysDummy) SystemMessage() {
+}
+
+func TestUnboundedLockfreeMailboxSysMessageConsistency(t *testing.T) {
+ max := 1000000
+ c := 100
+ var wg sync.WaitGroup
+ wg.Add(1)
+ p := UnboundedLockfree()
+ mi := &invoker{
+ max: max,
+ wg: &wg,
+ }
+ q := p()
+ q.RegisterHandlers(mi, NewDefaultDispatcher(300))
+
+ for j := 0; j < c; j++ {
+ cmax := max / c
+ go func(j int) {
+ for i := 0; i < cmax; i++ {
+ if rand.Intn(10) == 0 {
+ time.Sleep(time.Duration(rand.Intn(100)))
+ }
+ q.PostSystemMessage(
+ &sysDummy{
+ value: fmt.Sprintf("%v %v", j, i),
+ })
+ }
+ }(j)
+ }
+ wg.Wait()
+ time.Sleep(1 * time.Second)
+}
+
+func TestBoundedMailbox(t *testing.T) {
+ size := 3
+ m := boundedMailboxQueue{
+ userMailbox: rbqueue.NewRingBuffer(uint64(size)),
+ dropping: false,
+ }
+ m.Push("1")
+ m.Push("2")
+ m.Push("3")
+ assert.Equal(t, "1", m.Pop())
+}
+
+func TestBoundedDroppingMailbox(t *testing.T) {
+ size := 3
+ m := boundedMailboxQueue{
+ userMailbox: rbqueue.NewRingBuffer(uint64(size)),
+ dropping: true,
+ }
+ m.Push("1")
+ m.Push("2")
+ m.Push("3")
+ m.Push("4")
+ assert.Equal(t, "2", m.Pop())
+}
+
+func TestMailboxUserMessageCount(t *testing.T) {
+ max := 10
+ c := 10
+ var wg sync.WaitGroup
+ wg.Add(1)
+ p := UnboundedLockfree()
+ mi := &invoker{
+ max: max,
+ wg: &wg,
+ }
+ q := p()
+ q.RegisterHandlers(mi, NewDefaultDispatcher(300))
+
+ for j := 0; j < c; j++ {
+ q.PostUserMessage(fmt.Sprintf("%v", j))
+ }
+ assert.Equal(t, c, q.UserMessageCount())
+ wg.Wait()
+ time.Sleep(100 * time.Millisecond)
+}
diff --git a/actor/message.go b/actor/message.go
new file mode 100644
index 0000000000000000000000000000000000000000..718d0a63986034d10b921d07e1eec6f4067ec7d5
--- /dev/null
+++ b/actor/message.go
@@ -0,0 +1,25 @@
+package actor
+
+// The Producer type is a function that creates a new actor
+type Producer func() Actor
+
+// Actor is the interface that defines the Receive method.
+//
+// Receive is sent messages to be processed from the mailbox associated with the instance of the actor
+type Actor interface {
+ Receive(c Context)
+}
+
+// The ReceiveFunc type is an adapter to allow the use of ordinary functions as actors to process messages
+type ReceiveFunc func(c Context)
+
+// Receive calls f(c)
+func (f ReceiveFunc) Receive(c Context) {
+ f(c)
+}
+
+type ReceiverFunc func(c ReceiverContext, envelope *MessageEnvelope)
+
+type SenderFunc func(c SenderContext, target *PID, envelope *MessageEnvelope)
+
+type ContextDecoratorFunc func(ctx Context) Context
diff --git a/actor/message_batch.go b/actor/message_batch.go
new file mode 100644
index 0000000000000000000000000000000000000000..500ab6263d987c30bb68a6c928b6a5863cb19ae5
--- /dev/null
+++ b/actor/message_batch.go
@@ -0,0 +1,5 @@
+package actor
+
+type MessageBatch interface {
+ GetMessages() []interface{}
+}
diff --git a/actor/message_batch_test.go b/actor/message_batch_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..239b97abde5779eff1524c5d7940a8c2e2247d21
--- /dev/null
+++ b/actor/message_batch_test.go
@@ -0,0 +1,45 @@
+package actor
+
+import (
+ "sync"
+ "testing"
+)
+
+type dummyMessageBatch struct {
+ messages []interface{}
+}
+
+func (d dummyMessageBatch) GetMessages() []interface{} {
+ return d.messages
+}
+
+func TestActorReceivesEachMessageInAMessageBatch(t *testing.T) {
+ // each message in the batch
+ seenMessagesWg := sync.WaitGroup{}
+ seenMessagesWg.Add(10)
+
+ // the batch message itself
+ seenBatchMessageWg := sync.WaitGroup{}
+ seenBatchMessageWg.Add(1)
+
+ pid := rootContext.Spawn(PropsFromFunc(func(ctx Context) {
+ if _, ok := ctx.Message().(*DummyMessage); ok {
+ seenMessagesWg.Done()
+ }
+
+ if _, ok := ctx.Message().(*dummyMessageBatch); ok {
+ seenBatchMessageWg.Done()
+ }
+ }))
+
+ batch := &dummyMessageBatch{messages: make([]interface{}, 10)}
+
+ for i := 0; i < 10; i++ {
+ batch.messages[i] = &DummyMessage{}
+ }
+
+ rootContext.Send(pid, batch)
+
+ seenMessagesWg.Wait()
+ seenBatchMessageWg.Wait()
+}
diff --git a/actor/message_envelope.go b/actor/message_envelope.go
new file mode 100644
index 0000000000000000000000000000000000000000..a143d5e3de864d9181f5a7cdf7f058695bee8451
--- /dev/null
+++ b/actor/message_envelope.go
@@ -0,0 +1,95 @@
+package actor
+
+type messageHeader map[string]string
+
+func (header messageHeader) Get(key string) string {
+ return header[key]
+}
+
+func (header messageHeader) Set(key string, value string) {
+ header[key] = value
+}
+
+func (header messageHeader) Keys() []string {
+ keys := make([]string, 0, len(header))
+ for k := range header {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+func (header messageHeader) Length() int {
+ return len(header)
+}
+
+func (header messageHeader) ToMap() map[string]string {
+ mp := make(map[string]string)
+ for k, v := range header {
+ mp[k] = v
+ }
+ return mp
+}
+
+type ReadonlyMessageHeader interface {
+ Get(key string) string
+ Keys() []string
+ Length() int
+ ToMap() map[string]string
+}
+
+type MessageEnvelope struct {
+ Header messageHeader
+ Message interface{}
+ Sender *PID
+}
+
+func (envelope *MessageEnvelope) GetHeader(key string) string {
+ if envelope.Header == nil {
+ return ""
+ }
+ return envelope.Header.Get(key)
+}
+
+func (envelope *MessageEnvelope) SetHeader(key string, value string) {
+ if envelope.Header == nil {
+ envelope.Header = make(map[string]string)
+ }
+ envelope.Header.Set(key, value)
+}
+
+var EmptyMessageHeader = make(messageHeader)
+
+func WrapEnvelope(message interface{}) *MessageEnvelope {
+ if e, ok := message.(*MessageEnvelope); ok {
+ return e
+ }
+ return &MessageEnvelope{nil, message, nil}
+}
+
+func UnwrapEnvelope(message interface{}) (ReadonlyMessageHeader, interface{}, *PID) {
+ if env, ok := message.(*MessageEnvelope); ok {
+ return env.Header, env.Message, env.Sender
+ }
+ return nil, message, nil
+}
+
+func UnwrapEnvelopeHeader(message interface{}) ReadonlyMessageHeader {
+ if env, ok := message.(*MessageEnvelope); ok {
+ return env.Header
+ }
+ return nil
+}
+
+func UnwrapEnvelopeMessage(message interface{}) interface{} {
+ if env, ok := message.(*MessageEnvelope); ok {
+ return env.Message
+ }
+ return message
+}
+
+func UnwrapEnvelopeSender(message interface{}) *PID {
+ if env, ok := message.(*MessageEnvelope); ok {
+ return env.Sender
+ }
+ return nil
+}
diff --git a/actor/message_envelope_test.go b/actor/message_envelope_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d981aced8ac29e89ac0a795b83e77b18052e680a
--- /dev/null
+++ b/actor/message_envelope_test.go
@@ -0,0 +1,28 @@
+package actor
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNormalMessageGivesEmptyMessageHeaders(t *testing.T) {
+ t.Parallel()
+
+ props := PropsFromFunc(func(ctx Context) {
+ if _, ok := ctx.Message().(string); ok {
+ l := len(ctx.MessageHeader().Keys())
+ ctx.Respond(l)
+ }
+ })
+ a := rootContext.Spawn(props)
+
+ defer func() {
+ _ = rootContext.StopFuture(a).Wait()
+ }()
+
+ f := rootContext.RequestFuture(a, "hello", testTimeout)
+
+ res, _ := assertFutureSuccess(f, t).(int)
+ assert.Equal(t, 0, res)
+}
diff --git a/actor/messages.go b/actor/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..73e78ae56077c5217edcd756c69ea8e47fcb2720
--- /dev/null
+++ b/actor/messages.go
@@ -0,0 +1,108 @@
+package actor
+
+// ResumeMailbox is message sent by the actor system to resume mailbox processing.
+//
+// This will not be forwarded to the Receive method
+type ResumeMailbox struct{}
+
+// SuspendMailbox is message sent by the actor system to suspend mailbox processing.
+//
+// This will not be forwarded to the Receive method
+type SuspendMailbox struct{}
+
+type MailboxMessage interface {
+ MailboxMessage()
+}
+
+func (*SuspendMailbox) MailboxMessage() {}
+func (*ResumeMailbox) MailboxMessage() {}
+
+// InfrastructureMessage is a marker for all built in Proto.Actor messages
+type InfrastructureMessage interface {
+ InfrastructureMessage()
+}
+
+// IgnoreDeadLetterLogging messages are not logged in deadletter log
+type IgnoreDeadLetterLogging interface {
+ IgnoreDeadLetterLogging()
+}
+
+// An AutoReceiveMessage is a special kind of user message that will be handled in some way automatically by the actor
+type AutoReceiveMessage interface {
+ AutoReceiveMessage()
+}
+
+// NotInfluenceReceiveTimeout messages will not reset the ReceiveTimeout timer of an actor that receives the message
+type NotInfluenceReceiveTimeout interface {
+ NotInfluenceReceiveTimeout()
+}
+
+// A SystemMessage message is reserved for specific lifecycle messages used by the actor system
+type SystemMessage interface {
+ SystemMessage()
+}
+
+// A ReceiveTimeout message is sent to an actor after the Context.ReceiveTimeout duration has expired
+type ReceiveTimeout struct{}
+
+// A Restarting message is sent to an actor when the actor is being restarted by the system due to a failure
+type Restarting struct{}
+
+// A Stopping message is sent to an actor prior to the actor being stopped
+type Stopping struct{}
+
+// A Stopped message is sent to the actor once it has been stopped. A stopped actor will receive no further messages
+type Stopped struct{}
+
+// A Started message is sent to an actor once it has been started and ready to begin receiving messages.
+type Started struct{}
+
+// Restart is message sent by the actor system to control the lifecycle of an actor
+type Restart struct{}
+
+// Failure message is sent to an actor parent when an exception is thrown by one of its methods
+type Failure struct {
+ Who *PID
+ Reason interface{}
+ RestartStats *RestartStatistics
+ Message interface{}
+}
+
+type continuation struct {
+ message interface{}
+ f func()
+}
+
+func (*Touch) GetAutoResponse(ctx Context) interface{} {
+ return &Touched{
+ Who: ctx.Self(),
+ }
+}
+
+func (*Restarting) AutoReceiveMessage() {}
+func (*Stopping) AutoReceiveMessage() {}
+func (*Stopped) AutoReceiveMessage() {}
+func (*PoisonPill) AutoReceiveMessage() {}
+
+func (*Started) SystemMessage() {}
+func (*Stop) SystemMessage() {}
+func (*Watch) SystemMessage() {}
+func (*Unwatch) SystemMessage() {}
+func (*Terminated) SystemMessage() {}
+func (*Failure) SystemMessage() {}
+func (*Restart) SystemMessage() {}
+func (*continuation) SystemMessage() {}
+
+var (
+ restartingMessage AutoReceiveMessage = &Restarting{}
+ stoppingMessage AutoReceiveMessage = &Stopping{}
+ stoppedMessage AutoReceiveMessage = &Stopped{}
+ poisonPillMessage AutoReceiveMessage = &PoisonPill{}
+ receiveTimeoutMessage interface{} = &ReceiveTimeout{}
+ restartMessage SystemMessage = &Restart{}
+ startedMessage SystemMessage = &Started{}
+ stopMessage SystemMessage = &Stop{}
+ resumeMailboxMessage MailboxMessage = &ResumeMailbox{}
+ suspendMailboxMessage MailboxMessage = &SuspendMailbox{}
+ _ AutoRespond = &Touch{}
+)
diff --git a/actor/metrics.go b/actor/metrics.go
new file mode 100644
index 0000000000000000000000000000000000000000..9c82693e7e94994d49bcdec82dd01d65cdf700ab
--- /dev/null
+++ b/actor/metrics.go
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package actor
+
+import (
+ "fmt"
+ "strings"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+
+ "gitee.com/simplexyz/simpleactor-go/extensions"
+ "gitee.com/simplexyz/simpleactor-go/metrics"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/metric"
+)
+
+var extensionId = extensions.NextExtensionID()
+
+type Metrics struct {
+ metrics *metrics.ProtoMetrics
+ enabled bool
+}
+
+var _ extensions.Extension = &Metrics{}
+
+func (m *Metrics) Enabled() bool {
+ return m.enabled
+}
+
+func (m *Metrics) ExtensionID() extensions.ExtensionID {
+ return extensionId
+}
+
+func NewMetrics(provider metric.MeterProvider) *Metrics {
+ if provider == nil {
+ return &Metrics{}
+ }
+
+ return &Metrics{
+ metrics: metrics.NewProtoMetrics(provider),
+ enabled: true,
+ }
+}
+
+func (m *Metrics) PrepareMailboxLengthGauge() {
+ meter := otel.Meter(metrics.LibName)
+ gauge, err := meter.Int64ObservableGauge("protoactor_actor_mailbox_length",
+ metric.WithDescription("Actor's Mailbox Length"),
+ metric.WithUnit("1"))
+ if err != nil {
+ err = fmt.Errorf("failed to create ActorMailBoxLength instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+ m.metrics.Instruments().SetActorMailboxLengthGauge(gauge)
+}
+
+func (m *Metrics) CommonLabels(ctx Context) []attribute.KeyValue {
+ labels := []attribute.KeyValue{
+ attribute.String("address", ctx.ActorSystem().Address()),
+ attribute.String("actortype", strings.Replace(fmt.Sprintf("%T", ctx.Actor()), "*", "", 1)),
+ }
+
+ return labels
+}
diff --git a/actor/middleware/logging.go b/actor/middleware/logging.go
new file mode 100644
index 0000000000000000000000000000000000000000..932c543cb18ec0580dfb297668d05f8214477a18
--- /dev/null
+++ b/actor/middleware/logging.go
@@ -0,0 +1,19 @@
+package middleware
+
+import (
+ "log"
+ "reflect"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Logger is message middleware which logs messages before continuing to the next middleware.
+func Logger(next actor.ReceiverFunc) actor.ReceiverFunc {
+ fn := func(c actor.ReceiverContext, env *actor.MessageEnvelope) {
+ message := env.Message
+ log.Printf("%v got %v %+v", c.Self(), reflect.TypeOf(message), message)
+ next(c, env)
+ }
+
+ return fn
+}
diff --git a/actor/middleware/opentracing/activespan.go b/actor/middleware/opentracing/activespan.go
new file mode 100644
index 0000000000000000000000000000000000000000..cad67da994bf40e49d01d84e02ecb7d9f87edf9e
--- /dev/null
+++ b/actor/middleware/opentracing/activespan.go
@@ -0,0 +1,40 @@
+package opentracing
+
+import (
+ "fmt"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/opentracing/opentracing-go"
+)
+
+var activeSpan = sync.Map{}
+
+func getActiveSpan(pid *actor.PID) opentracing.Span {
+ value, ok := activeSpan.Load(pid)
+ if !ok {
+ return nil
+ }
+
+ span, _ := value.(opentracing.Span)
+
+ return span
+}
+
+func clearActiveSpan(pid *actor.PID) {
+ activeSpan.Delete(pid)
+}
+
+func setActiveSpan(pid *actor.PID, span opentracing.Span) {
+ activeSpan.Store(pid, span)
+}
+
+func GetActiveSpan(context actor.Context) opentracing.Span {
+ span := getActiveSpan(context.Self())
+ if span == nil {
+ // TODO: Fix finding the real span always or handle no-span better on receiving side
+ span = opentracing.StartSpan(fmt.Sprintf("%T/%T", context.Actor(), context.Message()))
+ }
+
+ return span
+}
diff --git a/actor/middleware/opentracing/envelope.go b/actor/middleware/opentracing/envelope.go
new file mode 100644
index 0000000000000000000000000000000000000000..1373fa5f5ccc4591c344f85513ba60496c12cf2b
--- /dev/null
+++ b/actor/middleware/opentracing/envelope.go
@@ -0,0 +1,37 @@
+package opentracing
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/opentracing/opentracing-go"
+)
+
+type messageHeaderReader struct {
+ ReadOnlyMessageHeader actor.ReadonlyMessageHeader
+}
+
+func (reader *messageHeaderReader) ForeachKey(handler func(key, val string) error) error {
+ if reader.ReadOnlyMessageHeader == nil {
+ return nil
+ }
+
+ for _, key := range reader.ReadOnlyMessageHeader.Keys() {
+ err := handler(key, reader.ReadOnlyMessageHeader.Get(key))
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+var _ opentracing.TextMapReader = &messageHeaderReader{}
+
+type messageEnvelopeWriter struct {
+ MessageEnvelope *actor.MessageEnvelope
+}
+
+func (writer *messageEnvelopeWriter) Set(key, val string) {
+ writer.MessageEnvelope.SetHeader(key, val)
+}
+
+var _ opentracing.TextMapWriter = &messageEnvelopeWriter{}
diff --git a/actor/middleware/opentracing/logger.go b/actor/middleware/opentracing/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..59d1bdfeb4b4a03d79345e577c46335c9f9d5804
--- /dev/null
+++ b/actor/middleware/opentracing/logger.go
@@ -0,0 +1,5 @@
+package opentracing
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var logger = log.New(log.ErrorLevel, "[TRACING]")
diff --git a/actor/middleware/opentracing/middlewarepropagation.go b/actor/middleware/opentracing/middlewarepropagation.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd622484d3412dd29d3e313d61ac6c3ddf3050dd
--- /dev/null
+++ b/actor/middleware/opentracing/middlewarepropagation.go
@@ -0,0 +1,15 @@
+package opentracing
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/actor/middleware/propagator"
+)
+
+func TracingMiddleware() actor.SpawnMiddleware {
+ return propagator.New().
+ WithItselfForwarded().
+ WithSpawnMiddleware(SpawnMiddleware()).
+ WithSenderMiddleware(SenderMiddleware()).
+ WithReceiverMiddleware(ReceiverMiddleware()).
+ SpawnMiddleware
+}
diff --git a/actor/middleware/opentracing/parentspan.go b/actor/middleware/opentracing/parentspan.go
new file mode 100644
index 0000000000000000000000000000000000000000..95f55b9f71ab248e051643e5e6f57de6827b7e85
--- /dev/null
+++ b/actor/middleware/opentracing/parentspan.go
@@ -0,0 +1,27 @@
+package opentracing
+
+import (
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/opentracing/opentracing-go"
+)
+
+var parentSpans = sync.Map{}
+
+func getAndClearParentSpan(pid *actor.PID) opentracing.Span {
+ value, ok := parentSpans.Load(pid)
+ if !ok {
+ return nil
+ }
+
+ parentSpans.Delete(pid)
+
+ span, _ := value.(opentracing.Span)
+
+ return span
+}
+
+func setParentSpan(pid *actor.PID, span opentracing.Span) {
+ parentSpans.Store(pid, span)
+}
diff --git a/actor/middleware/opentracing/receivermiddleware.go b/actor/middleware/opentracing/receivermiddleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..786cf223bcbcc3bb2221a2ffeb5b810348060a46
--- /dev/null
+++ b/actor/middleware/opentracing/receivermiddleware.go
@@ -0,0 +1,82 @@
+package opentracing
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/opentracing/opentracing-go"
+)
+
+func ReceiverMiddleware() actor.ReceiverMiddleware {
+ return func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ return func(c actor.ReceiverContext, envelope *actor.MessageEnvelope) {
+ spanContext, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, opentracing.TextMapReader(&messageHeaderReader{ReadOnlyMessageHeader: envelope.Header}))
+ if err == opentracing.ErrSpanContextNotFound {
+ logger.Debug("INBOUND No spanContext found", log.Stringer("PID", c.Self()), log.Error(err))
+ // next(c)
+ } else if err != nil {
+ logger.Debug("INBOUND Error", log.Stringer("PID", c.Self()), log.Error(err))
+ next(c, envelope)
+ return
+ }
+ var span opentracing.Span
+ switch envelope.Message.(type) {
+ case *actor.Started:
+ parentSpan := getAndClearParentSpan(c.Self())
+ if parentSpan != nil {
+ span = opentracing.StartSpan(fmt.Sprintf("%T/%T", c.Actor(), envelope.Message), opentracing.ChildOf(parentSpan.Context()))
+ logger.Debug("INBOUND Found parent span", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ } else {
+ logger.Debug("INBOUND No parent span", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ }
+ case *actor.Stopping:
+ var parentSpan opentracing.Span
+ if c.Parent() != nil {
+ parentSpan = getStoppingSpan(c.Parent())
+ }
+ if parentSpan != nil {
+ span = opentracing.StartSpan(fmt.Sprintf("%T/stopping", c.Actor()), opentracing.ChildOf(parentSpan.Context()))
+ } else {
+ span = opentracing.StartSpan(fmt.Sprintf("%T/stopping", c.Actor()))
+ }
+ setStoppingSpan(c.Self(), span)
+ span.SetTag("ActorPID", c.Self())
+ span.SetTag("ActorType", fmt.Sprintf("%T", c.Actor()))
+ span.SetTag("MessageType", fmt.Sprintf("%T", envelope.Message))
+ stoppingHandlingSpan := opentracing.StartSpan("stopping-handling", opentracing.ChildOf(span.Context()))
+ next(c, envelope)
+ stoppingHandlingSpan.Finish()
+ return
+ case *actor.Stopped:
+ span = getAndClearStoppingSpan(c.Self())
+ next(c, envelope)
+ if span != nil {
+ span.Finish()
+ }
+ return
+ }
+ if span == nil && spanContext == nil {
+ logger.Debug("INBOUND No spanContext. Starting new span", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ span = opentracing.StartSpan(fmt.Sprintf("%T/%T", c.Actor(), envelope.Message))
+ }
+ if span == nil {
+ logger.Debug("INBOUND Starting span from parent", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ span = opentracing.StartSpan(fmt.Sprintf("%T/%T", c.Actor(), envelope.Message), opentracing.ChildOf(spanContext))
+ }
+
+ setActiveSpan(c.Self(), span)
+ span.SetTag("ActorPID", c.Self())
+ span.SetTag("ActorType", fmt.Sprintf("%T", c.Actor()))
+ span.SetTag("MessageType", fmt.Sprintf("%T", envelope.Message))
+
+ defer func() {
+ logger.Debug("INBOUND Finishing span", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ span.Finish()
+ clearActiveSpan(c.Self())
+ }()
+
+ next(c, envelope)
+ }
+ }
+}
diff --git a/actor/middleware/opentracing/sendermiddleware.go b/actor/middleware/opentracing/sendermiddleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..f462c8e619eb3393c70ffd03359d2145aec15b9f
--- /dev/null
+++ b/actor/middleware/opentracing/sendermiddleware.go
@@ -0,0 +1,31 @@
+package opentracing
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/opentracing/opentracing-go"
+)
+
+func SenderMiddleware() actor.SenderMiddleware {
+ return func(next actor.SenderFunc) actor.SenderFunc {
+ return func(c actor.SenderContext, target *actor.PID, envelope *actor.MessageEnvelope) {
+ span := getActiveSpan(c.Self())
+
+ if span == nil {
+ logger.Debug("OUTBOUND No active span", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ next(c, target, envelope)
+ return
+ }
+
+ err := opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, opentracing.TextMapWriter(&messageEnvelopeWriter{MessageEnvelope: envelope}))
+ if err != nil {
+ logger.Debug("OUTBOUND Error injecting", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ next(c, target, envelope)
+ return
+ }
+
+ logger.Debug("OUTBOUND Successfully injected", log.Stringer("PID", c.Self()), log.TypeOf("ActorType", c.Actor()), log.TypeOf("MessageType", envelope.Message))
+ next(c, target, envelope)
+ }
+ }
+}
diff --git a/actor/middleware/opentracing/spawnmiddleware.go b/actor/middleware/opentracing/spawnmiddleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..a127c905ddfe979593c94d3ad16a68b2303f8090
--- /dev/null
+++ b/actor/middleware/opentracing/spawnmiddleware.go
@@ -0,0 +1,33 @@
+package opentracing
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ olog "github.com/opentracing/opentracing-go/log"
+)
+
+func SpawnMiddleware() actor.SpawnMiddleware {
+ return func(next actor.SpawnFunc) actor.SpawnFunc {
+ return func(actorSystem *actor.ActorSystem, id string, props *actor.Props, parentContext actor.SpawnerContext) (pid *actor.PID, e error) {
+ self := parentContext.Self()
+ pid, err := next(actorSystem, id, props, parentContext)
+ if err != nil {
+ logger.Debug("SPAWN got error trying to spawn", log.Stringer("PID", self), log.TypeOf("ActorType", parentContext.Actor()), log.Error(err))
+ return pid, err
+ }
+ if self != nil {
+ span := getActiveSpan(self)
+ if span != nil {
+ setParentSpan(pid, span)
+ span.LogFields(olog.String("SpawnPID", pid.String()))
+ logger.Debug("SPAWN found active span", log.Stringer("PID", self), log.TypeOf("ActorType", parentContext.Actor()), log.Stringer("SpawnedPID", pid))
+ } else {
+ logger.Debug("SPAWN no active span on parent", log.Stringer("PID", self), log.TypeOf("ActorType", parentContext.Actor()), log.Stringer("SpawnedPID", pid))
+ }
+ } else {
+ logger.Debug("SPAWN no parent pid", log.Stringer("SpawnedPID", pid))
+ }
+ return pid, err
+ }
+ }
+}
diff --git a/actor/middleware/opentracing/stoppingspan.go b/actor/middleware/opentracing/stoppingspan.go
new file mode 100644
index 0000000000000000000000000000000000000000..b01dd9224d75dfaf8b8cc18c41e3b39c02639c24
--- /dev/null
+++ b/actor/middleware/opentracing/stoppingspan.go
@@ -0,0 +1,31 @@
+package opentracing
+
+import (
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/opentracing/opentracing-go"
+)
+
+var stoppingSpans = sync.Map{}
+
+func getAndClearStoppingSpan(pid *actor.PID) opentracing.Span {
+ value, ok := stoppingSpans.Load(pid)
+ if !ok {
+ return nil
+ }
+ stoppingSpans.Delete(pid)
+ return value.(opentracing.Span)
+}
+
+func getStoppingSpan(pid *actor.PID) opentracing.Span {
+ value, ok := stoppingSpans.Load(pid)
+ if !ok {
+ return nil
+ }
+ return value.(opentracing.Span)
+}
+
+func setStoppingSpan(pid *actor.PID, span opentracing.Span) {
+ stoppingSpans.Store(pid, span)
+}
diff --git a/actor/middleware/opentracing/tracing_test.go b/actor/middleware/opentracing/tracing_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4fb5188ef1f75595af21f7edf9965402c8d18fea
--- /dev/null
+++ b/actor/middleware/opentracing/tracing_test.go
@@ -0,0 +1 @@
+package opentracing
diff --git a/actor/middleware/propagator/middlewarepropagation.go b/actor/middleware/propagator/middlewarepropagation.go
new file mode 100644
index 0000000000000000000000000000000000000000..58e30fba303d8cb852e8e27a09c625f5dd136b19
--- /dev/null
+++ b/actor/middleware/propagator/middlewarepropagation.go
@@ -0,0 +1,59 @@
+package propagator
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type MiddlewarePropagator struct {
+ spawnMiddleware []actor.SpawnMiddleware
+ senderMiddleware []actor.SenderMiddleware
+ receiverMiddleware []actor.ReceiverMiddleware
+ contextDecorators []actor.ContextDecorator
+}
+
+func New() *MiddlewarePropagator {
+ return &MiddlewarePropagator{}
+}
+
+func (propagator *MiddlewarePropagator) WithItselfForwarded() *MiddlewarePropagator {
+ return propagator.WithSpawnMiddleware(propagator.SpawnMiddleware)
+}
+
+func (propagator *MiddlewarePropagator) WithSpawnMiddleware(middleware ...actor.SpawnMiddleware) *MiddlewarePropagator {
+ propagator.spawnMiddleware = append(propagator.spawnMiddleware, middleware...)
+ return propagator
+}
+
+func (propagator *MiddlewarePropagator) WithSenderMiddleware(middleware ...actor.SenderMiddleware) *MiddlewarePropagator {
+ propagator.senderMiddleware = append(propagator.senderMiddleware, middleware...)
+ return propagator
+}
+
+func (propagator *MiddlewarePropagator) WithReceiverMiddleware(middleware ...actor.ReceiverMiddleware) *MiddlewarePropagator {
+ propagator.receiverMiddleware = append(propagator.receiverMiddleware, middleware...)
+ return propagator
+}
+
+func (propagator *MiddlewarePropagator) WithContextDecorator(decorators ...actor.ContextDecorator) *MiddlewarePropagator {
+ propagator.contextDecorators = append(propagator.contextDecorators, decorators...)
+ return propagator
+}
+
+func (propagator *MiddlewarePropagator) SpawnMiddleware(next actor.SpawnFunc) actor.SpawnFunc {
+ return func(actorSystem *actor.ActorSystem, id string, props *actor.Props, parentContext actor.SpawnerContext) (pid *actor.PID, e error) {
+ if propagator.spawnMiddleware != nil {
+ props = props.Configure(actor.WithSpawnMiddleware(propagator.spawnMiddleware...))
+ }
+ if propagator.senderMiddleware != nil {
+ props = props.Configure(actor.WithSenderMiddleware(propagator.senderMiddleware...))
+ }
+ if propagator.receiverMiddleware != nil {
+ props = props.Configure(actor.WithReceiverMiddleware(propagator.receiverMiddleware...))
+ }
+ if propagator.contextDecorators != nil {
+ props = props.Configure(actor.WithContextDecorator(propagator.contextDecorators...))
+ }
+ pid, err := next(actorSystem, id, props, parentContext)
+ return pid, err
+ }
+}
diff --git a/actor/middleware/propagator/middlewarepropagation_test.go b/actor/middleware/propagator/middlewarepropagation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..89a141a275f21387977866077c4d228368354bca
--- /dev/null
+++ b/actor/middleware/propagator/middlewarepropagation_test.go
@@ -0,0 +1,45 @@
+package propagator
+
+import (
+ "sync"
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPropagator(t *testing.T) {
+ mutex := &sync.Mutex{}
+ spawningCounter := 0
+ system := actor.NewActorSystem()
+
+ propagator := New().
+ WithItselfForwarded().
+ WithSpawnMiddleware(func(next actor.SpawnFunc) actor.SpawnFunc {
+ return func(actorSystem *actor.ActorSystem, id string, props *actor.Props, parentContext actor.SpawnerContext) (pid *actor.PID, e error) {
+ mutex.Lock()
+ spawningCounter++
+ mutex.Unlock()
+ return next(actorSystem, id, props, parentContext)
+ }
+ })
+
+ var start func(input int) *actor.Props
+ start = func(input int) *actor.Props {
+ return actor.PropsFromFunc(func(c actor.Context) {
+ switch c.Message().(type) {
+ case *actor.Started:
+ if input > 0 {
+ c.Spawn(start(input - 1))
+ }
+ }
+ })
+ }
+
+ rootContext := actor.NewRootContext(system, nil).WithSpawnMiddleware(propagator.SpawnMiddleware)
+ root := rootContext.Spawn(start(5))
+
+ _ = rootContext.StopFuture(root).Wait()
+
+ assert.Equal(t, spawningCounter, 5)
+}
diff --git a/actor/middleware/protozip/inbound_middleware.go b/actor/middleware/protozip/inbound_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c45d39a2790c45002be61274caa3f72248351ff
--- /dev/null
+++ b/actor/middleware/protozip/inbound_middleware.go
@@ -0,0 +1 @@
+package protozip
diff --git a/actor/middleware/protozip/outbound_middleware.go b/actor/middleware/protozip/outbound_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a260749bd9eb4254d9fb94db1b427beefbcba43
--- /dev/null
+++ b/actor/middleware/protozip/outbound_middleware.go
@@ -0,0 +1,17 @@
+package protozip
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+func ZipkinTracer(next actor.SenderFunc) actor.SenderFunc {
+ return func(ctx actor.SenderContext, target *actor.PID, envelope *actor.MessageEnvelope) {
+ header := ctx.MessageHeader()
+
+ envelope.SetHeader("trace-id", header.Get("trace-id"))
+ envelope.SetHeader("span-id", header.Get("child-id"))
+ envelope.SetHeader("child-id", "123random")
+
+ next(ctx, target, envelope)
+ }
+}
diff --git a/actor/middleware_chain.go b/actor/middleware_chain.go
new file mode 100644
index 0000000000000000000000000000000000000000..26aa585ca49a90588d0e7e59f1bfb3fbb58d1442
--- /dev/null
+++ b/actor/middleware_chain.go
@@ -0,0 +1,53 @@
+package actor
+
+func makeReceiverMiddlewareChain(receiverMiddleware []ReceiverMiddleware, lastReceiver ReceiverFunc) ReceiverFunc {
+ if len(receiverMiddleware) == 0 {
+ return nil
+ }
+
+ h := receiverMiddleware[len(receiverMiddleware)-1](lastReceiver)
+ for i := len(receiverMiddleware) - 2; i >= 0; i-- {
+ h = receiverMiddleware[i](h)
+ }
+
+ return h
+}
+
+func makeSenderMiddlewareChain(senderMiddleware []SenderMiddleware, lastSender SenderFunc) SenderFunc {
+ if len(senderMiddleware) == 0 {
+ return nil
+ }
+
+ h := senderMiddleware[len(senderMiddleware)-1](lastSender)
+ for i := len(senderMiddleware) - 2; i >= 0; i-- {
+ h = senderMiddleware[i](h)
+ }
+
+ return h
+}
+
+func makeContextDecoratorChain(decorator []ContextDecorator, lastDecorator ContextDecoratorFunc) ContextDecoratorFunc {
+ if len(decorator) == 0 {
+ return nil
+ }
+
+ h := decorator[len(decorator)-1](lastDecorator)
+ for i := len(decorator) - 2; i >= 0; i-- {
+ h = decorator[i](h)
+ }
+
+ return h
+}
+
+func makeSpawnMiddlewareChain(spawnMiddleware []SpawnMiddleware, lastSpawn SpawnFunc) SpawnFunc {
+ if len(spawnMiddleware) == 0 {
+ return nil
+ }
+
+ h := spawnMiddleware[len(spawnMiddleware)-1](lastSpawn)
+ for i := len(spawnMiddleware) - 2; i >= 0; i-- {
+ h = spawnMiddleware[i](h)
+ }
+
+ return h
+}
diff --git a/actor/middleware_chain_test.go b/actor/middleware_chain_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0c5fe994e2102e5e91f6b78f883c870f85f7e9f
--- /dev/null
+++ b/actor/middleware_chain_test.go
@@ -0,0 +1,46 @@
+package actor
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func middleware(called *int) ReceiverMiddleware {
+ return func(next ReceiverFunc) ReceiverFunc {
+ fn := func(ctx ReceiverContext, env *MessageEnvelope) {
+ env.Message = env.Message.(int) + 1
+ *called = env.Message.(int)
+
+ next(ctx, env)
+ }
+ return fn
+ }
+}
+
+func TestMakeReceiverMiddleware_CallsInCorrectOrder(t *testing.T) {
+ var c [3]int
+
+ r := []ReceiverMiddleware{
+ middleware(&c[0]),
+ middleware(&c[1]),
+ middleware(&c[2]),
+ }
+
+ mc := &mockContext{}
+
+ env := &MessageEnvelope{
+ Message: 0,
+ }
+
+ chain := makeReceiverMiddlewareChain(r, func(receiver ReceiverContext, env *MessageEnvelope) {})
+ chain(mc, env)
+
+ assert.Equal(t, 1, c[0])
+ assert.Equal(t, 2, c[1])
+ assert.Equal(t, 3, c[2])
+}
+
+func TestMakeInboundMiddleware_ReturnsNil(t *testing.T) {
+ assert.Nil(t, makeReceiverMiddlewareChain([]ReceiverMiddleware{}, func(_ ReceiverContext, _ *MessageEnvelope) {}))
+}
diff --git a/actor/pid.go b/actor/pid.go
new file mode 100644
index 0000000000000000000000000000000000000000..347515438d142addfe9868973e73a32ed7ff79bc
--- /dev/null
+++ b/actor/pid.go
@@ -0,0 +1,72 @@
+package actor
+
+import (
+ "sync/atomic"
+ "unsafe"
+)
+
+/*
+ensure the generated pid file contains the p *Process
+TODO: make some sed command to inject this somehow
+
+type PID struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Address string `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"`
+ Id string `protobuf:"bytes,2,opt,name=Id,proto3" json:"Id,omitempty"`
+ RequestId uint32 `protobuf:"varint,3,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+
+ //manually added
+ p *Process
+}
+*/
+
+//goland:noinspection GoReceiverNames
+func (pid *PID) ref(actorSystem *ActorSystem) Process {
+ p := (*Process)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&pid.p))))
+ if p != nil {
+ if l, ok := (*p).(*ActorProcess); ok && atomic.LoadInt32(&l.dead) == 1 {
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&pid.p)), nil)
+ } else {
+ return *p
+ }
+ }
+
+ ref, exists := actorSystem.ProcessRegistry.Get(pid)
+ if exists {
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&pid.p)), unsafe.Pointer(&ref))
+ }
+
+ return ref
+}
+
+// sendUserMessage sends a messages asynchronously to the PID.
+//
+//goland:noinspection GoReceiverNames
+func (pid *PID) sendUserMessage(actorSystem *ActorSystem, message interface{}) {
+ pid.ref(actorSystem).SendUserMessage(pid, message)
+}
+
+//goland:noinspection GoReceiverNames.
+func (pid *PID) sendSystemMessage(actorSystem *ActorSystem, message interface{}) {
+ pid.ref(actorSystem).SendSystemMessage(pid, message)
+}
+
+//goland:noinspection GoReceiverNames.
+func (pid *PID) Equal(other *PID) bool {
+ if pid != nil && other == nil {
+ return false
+ }
+
+ return pid.ID == other.ID && pid.Address == other.Address && pid.RequestID == other.RequestID
+}
+
+// NewPID returns a new instance of the PID struct.
+func NewPID(address, id string) *PID {
+ return &PID{
+ Address: address,
+ ID: id,
+ }
+}
diff --git a/actor/pid.rpc.go b/actor/pid.rpc.go
new file mode 100644
index 0000000000000000000000000000000000000000..80e33ac7393cf40be9f1e1cd2a24e3bac2279143
--- /dev/null
+++ b/actor/pid.rpc.go
@@ -0,0 +1,62 @@
+package actor
+
+import "sync"
+
+const PH = 1
+
+func (m *PID) ResetEx() {
+ if m == nil {
+ return
+ }
+ m.Address = ""
+ m.ID = ""
+ m.p = nil
+}
+
+func (m *PID) Clone() *PID {
+ if m == nil {
+ return nil
+ }
+ return &PID{
+ Address: m.Address,
+ ID: m.ID,
+ p: m.p,
+ }
+}
+
+func Clone_PID_Slice(dest []*PID, src []*PID) []*PID {
+ if len(src) > 0 {
+ dest = make([]*PID, len(src))
+ for i, e := range src {
+ if e != nil {
+ dest[i] = e.Clone()
+ }
+ }
+ } else {
+ //dest = []*PID{}
+ dest = nil
+ }
+ return dest
+}
+
+var g_PID_Pool = sync.Pool{}
+
+func Get_PID() *PID {
+ m, ok := g_PID_Pool.Get().(*PID)
+ if !ok {
+ m = NewPID("", "")
+ } else {
+ if m == nil {
+ m = NewPID("", "")
+ } else {
+ m.ResetEx()
+ }
+ }
+ return m
+}
+
+func Put_PID(i interface{}) {
+ if m, ok := i.(*PID); ok && m != nil {
+ g_PID_Pool.Put(i)
+ }
+}
diff --git a/actor/pid_test.go b/actor/pid_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..3d8ca54826ed0c02efc72be376743653de9934ed
--- /dev/null
+++ b/actor/pid_test.go
@@ -0,0 +1,40 @@
+package actor
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type ShortLivingActor struct{}
+
+func (sl *ShortLivingActor) Receive(Context) {
+}
+
+func TestStopFuture(t *testing.T) {
+ plog.Debug("hello world")
+
+ ID := "UniqueID"
+ {
+ props := PropsFromProducer(func() Actor { return &ShortLivingActor{} })
+ a, _ := rootContext.SpawnNamed(props, ID)
+
+ fut := rootContext.StopFuture(a)
+
+ res, errR := fut.Result()
+ if errR != nil {
+ assert.Fail(t, "Failed to wait stop actor %pids", errR)
+ return
+ }
+
+ _, ok := res.(*Terminated)
+ if !ok {
+ assert.Fail(t, "Cannot cast %pids", reflect.TypeOf(res))
+ return
+ }
+
+ _, found := system.ProcessRegistry.Get(a)
+ assert.False(t, found)
+ }
+}
diff --git a/actor/pidset.go b/actor/pidset.go
new file mode 100644
index 0000000000000000000000000000000000000000..7fb399f3fcfd0050772c1d18ee3652b2ef00a68c
--- /dev/null
+++ b/actor/pidset.go
@@ -0,0 +1,112 @@
+package actor
+
+type PIDSet struct {
+ pids []*PID
+ lookup map[pidKey]int
+}
+
+// pidKey is used as a key in the lookup map to avoid allocations.
+type pidKey struct {
+ address string
+ id string
+}
+
+func (p *PIDSet) key(pid *PID) pidKey {
+ return pidKey{address: pid.Address, id: pid.ID}
+}
+
+// NewPIDSet returns a new PIDSet with the given pids.
+func NewPIDSet(pids ...*PID) *PIDSet {
+ p := &PIDSet{}
+ for _, pid := range pids {
+ p.Add(pid)
+ }
+ return p
+}
+
+func (p *PIDSet) ensureInit() {
+ if p.lookup == nil {
+ p.lookup = make(map[pidKey]int)
+ }
+}
+
+func (p *PIDSet) indexOf(v *PID) int {
+ if idx, ok := p.lookup[p.key(v)]; ok {
+ return idx
+ }
+
+ return -1
+}
+
+func (p *PIDSet) Contains(v *PID) bool {
+ _, ok := p.lookup[p.key(v)]
+ return ok
+}
+
+// Add adds the element v to the set.
+func (p *PIDSet) Add(v *PID) {
+ p.ensureInit()
+ if p.Contains(v) {
+ return
+ }
+
+ p.pids = append(p.pids, v)
+ p.lookup[p.key(v)] = len(p.pids) - 1
+}
+
+// Remove removes v from the set and returns true if them element existed.
+func (p *PIDSet) Remove(v *PID) bool {
+ p.ensureInit()
+ i := p.indexOf(v)
+ if i == -1 {
+ return false
+ }
+
+ delete(p.lookup, p.key(v))
+ if i < len(p.pids)-1 {
+ lastPID := p.pids[len(p.pids)-1]
+
+ p.pids[i] = lastPID
+ p.lookup[p.key(lastPID)] = i
+ }
+
+ p.pids = p.pids[:len(p.pids)-1]
+
+ return true
+}
+
+// Len returns the number of elements in the set.
+func (p *PIDSet) Len() int {
+ return len(p.pids)
+}
+
+// Clear removes all the elements in the set.
+func (p *PIDSet) Clear() {
+ p.pids = p.pids[:0]
+ p.lookup = make(map[pidKey]int)
+}
+
+// Empty reports whether the set is empty.
+func (p *PIDSet) Empty() bool {
+ return p.Len() == 0
+}
+
+// Values returns all the elements of the set as a slice.
+func (p *PIDSet) Values() []*PID {
+ return p.pids
+}
+
+// ForEach invokes f for every element of the set.
+func (p *PIDSet) ForEach(f func(i int, pid *PID)) {
+ for i, pid := range p.pids {
+ f(i, pid)
+ }
+}
+
+func (p *PIDSet) Get(index int) *PID {
+ return p.pids[index]
+}
+
+func (p *PIDSet) Clone() *PIDSet {
+ return NewPIDSet(p.pids...)
+}
diff --git a/actor/pidset_test.go b/actor/pidset_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c4f70c497afd26411d8d865a00273e49cc4f8693
--- /dev/null
+++ b/actor/pidset_test.go
@@ -0,0 +1,164 @@
+package actor
+
+import (
+ "math/rand"
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPIDSet_Empty(t *testing.T) {
+ var s PIDSet
+ assert.True(t, s.Empty())
+}
+
+func TestPIDSet_Clear(t *testing.T) {
+ var s PIDSet
+ s.Add(NewPID(localAddress, "p1"))
+ s.Add(NewPID(localAddress, "p2"))
+ s.Add(NewPID(localAddress, "p3"))
+ assert.Equal(t, 3, s.Len())
+ s.Clear()
+ assert.True(t, s.Empty())
+ assert.Len(t, s.pids, 0)
+}
+
+func TestPIDSet_Remove(t *testing.T) {
+ var s PIDSet
+ s.Add(NewPID(localAddress, "p1"))
+ s.Add(NewPID(localAddress, "p2"))
+ s.Add(NewPID(localAddress, "p3"))
+ assert.Equal(t, 3, s.Len())
+
+ s.Remove(NewPID(localAddress, "p3"))
+ assert.Equal(t, 2, s.Len())
+ assert.False(t, s.Contains(NewPID(localAddress, "p3")))
+}
+
+func TestPIDSet_AddSmall(t *testing.T) {
+ s := NewPIDSet()
+ p1 := NewPID(localAddress, "p1")
+ s.Add(p1)
+ assert.False(t, s.Empty())
+ p1 = NewPID(localAddress, "p1")
+ s.Add(p1)
+ assert.Equal(t, 1, s.Len())
+}
+
+func TestPIDSet_Values(t *testing.T) {
+ var s PIDSet
+ s.Add(NewPID(localAddress, "p1"))
+ s.Add(NewPID(localAddress, "p2"))
+ s.Add(NewPID(localAddress, "p3"))
+ assert.False(t, s.Empty())
+
+ r := s.Values()
+ assert.Len(t, r, 3)
+}
+
+func TestPIDSet_AddMap(t *testing.T) {
+ s := NewPIDSet()
+ p1 := NewPID(localAddress, "p1")
+ s.Add(p1)
+ assert.False(t, s.Empty())
+ p1 = NewPID(localAddress, "p1")
+ s.Add(p1)
+ assert.Equal(t, 1, s.Len())
+}
+
+var pids []*PID
+
+func init() {
+ for i := 0; i < 100000; i++ {
+ pids = append(pids, NewPID(localAddress, "p"+strconv.Itoa(i)))
+ }
+}
+
+func BenchmarkPIDSet_Add(b *testing.B) {
+ cases := []struct {
+ l int
+ }{
+ {l: 1},
+ {l: 5},
+ {l: 20},
+ {l: 500},
+ }
+
+ for _, tc := range cases {
+ b.Run("len "+strconv.Itoa(tc.l), func(b *testing.B) {
+ pidSetAdd(b, pids[:tc.l])
+ })
+ }
+}
+
+func pidSetAdd(b *testing.B, data []*PID) {
+ for i := 0; i < b.N; i++ {
+ var s PIDSet
+ for j := 0; j < len(data); j++ {
+ s.Add(data[j])
+ }
+ }
+}
+
+func BenchmarkPIDSet_AddRemove(b *testing.B) {
+ cases := []struct {
+ l int
+ }{
+ {l: 1},
+ {l: 5},
+ {l: 20},
+ {l: 500},
+ }
+
+ for _, tc := range cases {
+ b.Run("len "+strconv.Itoa(tc.l), func(b *testing.B) {
+ pidSetAddRemove(b, pids[:tc.l])
+ })
+ }
+}
+
+func pidSetAddRemove(b *testing.B, data []*PID) {
+ for i := 0; i < b.N; i++ {
+ var s PIDSet
+ for j := 0; j < len(data); j++ {
+ s.Add(data[j])
+ }
+ for j := 0; j < len(data); j++ {
+ s.Remove(data[j])
+ }
+ }
+}
+
+func BenchmarkPIDSet(b *testing.B) {
+ cases := []struct {
+ l int
+ }{
+ {l: 1},
+ {l: 5},
+ {l: 20},
+ {l: 500},
+ {l: 1000},
+ {l: 10000},
+ {l: 100000},
+ }
+
+ for _, tc := range cases {
+ b.Run("len "+strconv.Itoa(tc.l), func(b *testing.B) {
+ b.StopTimer()
+ var s PIDSet
+ for i := 0; i < tc.l; i++ {
+ s.Add(pids[i])
+ }
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ pid := pids[rand.Intn(len(pids))]
+
+ s.Add(pid)
+
+ s.Remove(s.Get(rand.Intn(s.Len())))
+ }
+ })
+ }
+}
diff --git a/actor/priority_queue.go b/actor/priority_queue.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8404f0592ee69c4e0277ec2e1fef60097dda510
--- /dev/null
+++ b/actor/priority_queue.go
@@ -0,0 +1,70 @@
+package actor
+
+// A priority queue is a sort of meta-queue that uses a queue per priority level.
+// The underlying queues can be anything that implements the queue interface.
+//
+// Messages that implement the PriorityMessage interface (i.e. have a GetPriority
+// method) will be consumed in priority order first, queue order second. So if a
+// higher priority message arrives, it will jump to the front of the queue from
+// the consumer's perspective.
+//
+// There are 8 priority levels (0-7) because having too many levels impacts
+// performance. And 8 priority levels ought to be enough for anybody. ;)
+// This means your GetPriority method should return int8s between 0 and 7. If any
+// return values are higher or lower, they will be reset to 7 or 0, respectively.
+//
+// The default priority level is 4 for messages that don't implement PriorityMessage.
+// If you want your message processed sooner than un-prioritized messages, have its
+// GetPriority method return a larger int8 value.
+// Likewise, if you'd like to de-prioritize your message, have its GetPriority method
+// return an int8 less than 4.
+
+const (
+ priorityLevels = 8
+ DefaultPriority = int8(priorityLevels / 2)
+)
+
+type PriorityMessage interface {
+ GetPriority() int8
+}
+
+type priorityQueue struct {
+ priorityQueues []queue
+}
+
+func NewPriorityQueue(queueProducer func() queue) *priorityQueue {
+ q := &priorityQueue{
+ priorityQueues: make([]queue, priorityLevels),
+ }
+
+ for p := 0; p < priorityLevels; p++ {
+ q.priorityQueues[p] = queueProducer()
+ }
+
+ return q
+}
+
+func (q *priorityQueue) Push(item interface{}) {
+ itemPriority := DefaultPriority
+
+ if priorityItem, ok := item.(PriorityMessage); ok {
+ itemPriority = priorityItem.GetPriority()
+ if itemPriority < 0 {
+ itemPriority = 0
+ }
+ if itemPriority > priorityLevels-1 {
+ itemPriority = priorityLevels - 1
+ }
+ }
+
+ q.priorityQueues[itemPriority].Push(item)
+}
+
+func (q *priorityQueue) Pop() interface{} {
+ for p := priorityLevels - 1; p >= 0; p-- {
+ if item := q.priorityQueues[p].Pop(); item != nil {
+ return item
+ }
+ }
+ return nil
+}
diff --git a/actor/priority_queue_test.go b/actor/priority_queue_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6919c581c6e1e9635c1d4b4687931acf6c971bdd
--- /dev/null
+++ b/actor/priority_queue_test.go
@@ -0,0 +1,193 @@
+package actor
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/goring"
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+)
+
+type Message interface {
+ GetMessage() string
+}
+
+type TestPriorityMessage struct {
+ message string
+ priority int8
+}
+
+type TestMessage struct {
+ message string
+}
+
+func (tpm *TestPriorityMessage) GetPriority() int8 {
+ return tpm.priority
+}
+
+func (tpm *TestPriorityMessage) GetMessage() string {
+ return tpm.message
+}
+
+func (tm *TestMessage) GetMessage() string {
+ return tm.message
+}
+
+func newTestGoringPriorityQueue() *priorityQueue {
+ return NewPriorityQueue(func() queue {
+ return &unboundedMailboxQueue{
+ userMailbox: goring.New(1),
+ }
+ })
+}
+
+func newTestMpscPriorityQueue() *priorityQueue {
+ return NewPriorityQueue(func() queue {
+ return mpsc.New()
+ })
+}
+
+func TestPushPopGoring(t *testing.T) {
+ q := newTestGoringPriorityQueue()
+ q.Push("hello")
+ res := q.Pop()
+ assert.Equal(t, "hello", res)
+}
+
+func TestPushPopGoringPriority(t *testing.T) {
+ q := newTestGoringPriorityQueue()
+
+ // pushes
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "7 hello",
+ priority: 7,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "5 hello",
+ priority: 5,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "0 hello",
+ priority: 0,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "6 hello",
+ priority: 6,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestMessage{message: "hello"})
+ }
+
+ // pops in priority order
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "7 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "6 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "5 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "0 hello", res.(Message).GetMessage())
+ }
+}
+
+func TestPushPopMpsc(t *testing.T) {
+ q := newTestMpscPriorityQueue()
+ q.Push("hello")
+ res := q.Pop()
+ assert.Equal(t, "hello", res)
+}
+
+func TestPushPopMpscPriority(t *testing.T) {
+ q := newTestMpscPriorityQueue()
+
+ // pushes
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "7 hello",
+ priority: 7,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "5 hello",
+ priority: 5,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "0 hello",
+ priority: 0,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestPriorityMessage{
+ message: "6 hello",
+ priority: 6,
+ })
+ }
+
+ for i := 0; i < 2; i++ {
+ q.Push(&TestMessage{message: "hello"})
+ }
+
+ // pops in priority order
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "7 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "6 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "5 hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "hello", res.(Message).GetMessage())
+ }
+
+ for i := 0; i < 2; i++ {
+ res := q.Pop()
+ assert.Equal(t, "0 hello", res.(Message).GetMessage())
+ }
+}
diff --git a/actor/process.go b/actor/process.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ea0611076fe5aca42b1ab396657d557141504a7
--- /dev/null
+++ b/actor/process.go
@@ -0,0 +1,8 @@
+package actor
+
+// A Process is an interface that defines the base contract for interaction of actors
+type Process interface {
+ SendUserMessage(pid *PID, message interface{})
+ SendSystemMessage(pid *PID, message interface{})
+ Stop(pid *PID)
+}
diff --git a/actor/process_registry.go b/actor/process_registry.go
new file mode 100644
index 0000000000000000000000000000000000000000..e96ef4bf3723495fbd853cd821f894b55b8d406f
--- /dev/null
+++ b/actor/process_registry.go
@@ -0,0 +1,141 @@
+package actor
+
+import (
+ "sync/atomic"
+
+ murmur32 "github.com/twmb/murmur3"
+
+ cmap "github.com/orcaman/concurrent-map"
+)
+
+type ProcessRegistryValue struct {
+ SequenceID uint64
+ ActorSystem *ActorSystem
+ Address string
+ LocalPIDs *SliceMap
+ RemoteHandlers []AddressResolver
+}
+
+type SliceMap struct {
+ LocalPIDs []cmap.ConcurrentMap
+}
+
+func newSliceMap() *SliceMap {
+ sm := &SliceMap{}
+ sm.LocalPIDs = make([]cmap.ConcurrentMap, 1024)
+
+ for i := 0; i < len(sm.LocalPIDs); i++ {
+ sm.LocalPIDs[i] = cmap.New()
+ }
+
+ return sm
+}
+
+func (s *SliceMap) GetBucket(key string) cmap.ConcurrentMap {
+ hash := murmur32.Sum32([]byte(key))
+ index := int(hash) % len(s.LocalPIDs)
+
+ return s.LocalPIDs[index]
+}
+
+const (
+ localAddress = "nonhost"
+)
+
+func NewProcessRegistry(actorSystem *ActorSystem) *ProcessRegistryValue {
+ return &ProcessRegistryValue{
+ ActorSystem: actorSystem,
+ Address: localAddress,
+ LocalPIDs: newSliceMap(),
+ }
+}
+
+// An AddressResolver is used to resolve remote actors
+type AddressResolver func(*PID) (Process, bool)
+
+func (pr *ProcessRegistryValue) RegisterAddressResolver(handler AddressResolver) {
+ pr.RemoteHandlers = append(pr.RemoteHandlers, handler)
+}
+
+const (
+ digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~+"
+)
+
+func uint64ToId(u uint64) string {
+ var buf [13]byte
+ i := 13
+ // base is power of 2: use shifts and masks instead of / and %
+ for u >= 64 {
+ i--
+ buf[i] = digits[uintptr(u)&0x3f]
+ u >>= 6
+ }
+ // u < base
+ i--
+ buf[i] = digits[uintptr(u)]
+ i--
+ buf[i] = '$'
+
+ return string(buf[i:])
+}
+
+func (pr *ProcessRegistryValue) NextId() string {
+ counter := atomic.AddUint64(&pr.SequenceID, 1)
+
+ return uint64ToId(counter)
+}
+
+func (pr *ProcessRegistryValue) Add(process Process, id string) (*PID, bool) {
+ bucket := pr.LocalPIDs.GetBucket(id)
+
+ return &PID{
+ Address: pr.Address,
+ ID: id,
+ }, bucket.SetIfAbsent(id, process)
+}
+
+func (pr *ProcessRegistryValue) Remove(pid *PID) {
+ bucket := pr.LocalPIDs.GetBucket(pid.ID)
+
+ ref, _ := bucket.Pop(pid.ID)
+ if l, ok := ref.(*ActorProcess); ok {
+ atomic.StoreInt32(&l.dead, 1)
+ }
+}
+
+func (pr *ProcessRegistryValue) Get(pid *PID) (Process, bool) {
+ if pid == nil {
+ return pr.ActorSystem.DeadLetter, false
+ }
+
+ if pid.Address != localAddress && pid.Address != pr.Address {
+ for _, handler := range pr.RemoteHandlers {
+ ref, ok := handler(pid)
+ if ok {
+ return ref, true
+ }
+ }
+
+ return pr.ActorSystem.DeadLetter, false
+ }
+
+ bucket := pr.LocalPIDs.GetBucket(pid.ID)
+ ref, ok := bucket.Get(pid.ID)
+
+ if !ok {
+ return pr.ActorSystem.DeadLetter, false
+ }
+
+ return ref.(Process), true
+}
+
+func (pr *ProcessRegistryValue) GetLocal(id string) (Process, bool) {
+ bucket := pr.LocalPIDs.GetBucket(id)
+ ref, ok := bucket.Get(id)
+
+ if !ok {
+ return pr.ActorSystem.DeadLetter, false
+ }
+
+ return ref.(Process), true
+}
diff --git a/actor/process_registry_test.go b/actor/process_registry_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..14676c484d83d7de46c9de58ed7fdd1d1bc533e9
--- /dev/null
+++ b/actor/process_registry_test.go
@@ -0,0 +1,46 @@
+package actor
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestUint64ToId(t *testing.T) {
+ cases := []struct {
+ i uint64
+ e string
+ }{
+ {0xfedcba9876543210, "$fXsKFxSl38g"},
+ {0x0, "$0"},
+ {0x1, "$1"},
+ {0xf, "$f"},
+ {0x1041041041041041, "$11111111111"},
+ }
+ for _, tc := range cases {
+ t.Run(tc.e, func(t *testing.T) {
+ s := uint64ToId(tc.i)
+ assert.Equal(t, tc.e, s)
+ })
+ }
+}
+
+var ss string
+
+func BenchmarkUint64ToId(b *testing.B) {
+ var s string
+ for i := 0; i < b.N; i++ {
+ s = uint64ToId(uint64(i) << 5)
+ }
+ ss = s
+}
+
+func BenchmarkUint64ToString2(b *testing.B) {
+ var s string
+ var buf [12]byte
+ for i := 0; i < b.N; i++ {
+ s = string(strconv.AppendUint(buf[:], uint64(i)<<5, 36))
+ }
+ ss = s
+}
diff --git a/actor/props.go b/actor/props.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f11fc7b3b14a908fa453ea6428133849ec2619f
--- /dev/null
+++ b/actor/props.go
@@ -0,0 +1,155 @@
+package actor
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/metrics"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/metric"
+)
+
+type (
+ SpawnFunc func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (*PID, error)
+ ReceiverMiddleware func(next ReceiverFunc) ReceiverFunc
+ SenderMiddleware func(next SenderFunc) SenderFunc
+ ContextDecorator func(next ContextDecoratorFunc) ContextDecoratorFunc
+ SpawnMiddleware func(next SpawnFunc) SpawnFunc
+)
+
+// Default values.
+var (
+ defaultDispatcher = NewDefaultDispatcher(300)
+ defaultMailboxProducer = Unbounded()
+ defaultSpawner = func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (*PID, error) {
+ ctx := newActorContext(actorSystem, props, parentContext.Self())
+ mb := props.produceMailbox()
+
+ // prepare the mailbox number counter
+ if ctx.actorSystem.Config.MetricsProvider != nil {
+ sysMetrics, ok := ctx.actorSystem.Extensions.Get(extensionId).(*Metrics)
+ if ok && sysMetrics.enabled {
+ if instruments := sysMetrics.metrics.Get(metrics.InternalActorMetrics); instruments != nil {
+ sysMetrics.PrepareMailboxLengthGauge()
+ meter := otel.Meter(metrics.LibName)
+
+ if _, err := meter.RegisterCallback(func(_ context.Context, o metric.Observer) error {
+ o.ObserveInt64(instruments.ActorMailboxLength, int64(mb.UserMessageCount()), metric.WithAttributes(sysMetrics.CommonLabels(ctx)...))
+ return nil
+ }); err != nil {
+ err = fmt.Errorf("failed to instrument Actor Mailbox, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+ }
+ }
+ }
+
+ dp := props.getDispatcher()
+ proc := NewActorProcess(mb)
+ pid, absent := actorSystem.ProcessRegistry.Add(proc, id)
+ if !absent {
+ return pid, ErrNameExists
+ }
+ ctx.self = pid
+
+ initialize(props, ctx)
+
+ mb.RegisterHandlers(ctx, dp)
+ mb.PostSystemMessage(startedMessage)
+ mb.Start()
+
+ return pid, nil
+ }
+ defaultContextDecorator = func(ctx Context) Context {
+ return ctx
+ }
+)
+
+func initialize(props *Props, ctx *actorContext) {
+ if props.onInit == nil {
+ return
+ }
+
+ for _, init := range props.onInit {
+ init(ctx)
+ }
+}
+
+// DefaultSpawner this is a hacking way to allow Proto.Router access default spawner func.
+var DefaultSpawner SpawnFunc = defaultSpawner
+
+// ErrNameExists is the error used when an existing name is used for spawning an actor.
+var ErrNameExists = errors.New("spawn: name exists")
+
+// Props represents configuration to define how an actor should be created.
+type Props struct {
+ spawner SpawnFunc
+ producer Producer
+ mailboxProducer MailboxProducer
+ guardianStrategy SupervisorStrategy
+ supervisionStrategy SupervisorStrategy
+ dispatcher Dispatcher
+ receiverMiddleware []ReceiverMiddleware
+ senderMiddleware []SenderMiddleware
+ spawnMiddleware []SpawnMiddleware
+ receiverMiddlewareChain ReceiverFunc
+ senderMiddlewareChain SenderFunc
+ spawnMiddlewareChain SpawnFunc
+ contextDecorator []ContextDecorator
+ contextDecoratorChain ContextDecoratorFunc
+ onInit []func(ctx Context)
+}
+
+func (props *Props) getSpawner() SpawnFunc {
+ if props.spawner == nil {
+ return defaultSpawner
+ }
+
+ return props.spawner
+}
+
+func (props *Props) getDispatcher() Dispatcher {
+ if props.dispatcher == nil {
+ return defaultDispatcher
+ }
+
+ return props.dispatcher
+}
+
+func (props *Props) getSupervisor() SupervisorStrategy {
+ if props.supervisionStrategy == nil {
+ return defaultSupervisionStrategy
+ }
+
+ return props.supervisionStrategy
+}
+
+func (props *Props) getContextDecoratorChain() ContextDecoratorFunc {
+ if props.contextDecoratorChain == nil {
+ return defaultContextDecorator
+ }
+
+ return props.contextDecoratorChain
+}
+
+func (props *Props) produceMailbox() Mailbox {
+ if props.mailboxProducer == nil {
+ return defaultMailboxProducer()
+ }
+
+ return props.mailboxProducer()
+}
+
+func (props *Props) spawn(actorSystem *ActorSystem, name string, parentContext SpawnerContext) (*PID, error) {
+ return props.getSpawner()(actorSystem, name, props, parentContext)
+}
+
+func (props *Props) Configure(opts ...PropsOption) *Props {
+ for _, opt := range opts {
+ opt(props)
+ }
+
+ return props
+}
diff --git a/actor/props_opts.go b/actor/props_opts.go
new file mode 100644
index 0000000000000000000000000000000000000000..542b4988252831a89e2a7c4d1bdd61167a54dd70
--- /dev/null
+++ b/actor/props_opts.go
@@ -0,0 +1,135 @@
+package actor
+
+type PropsOption func(props *Props)
+
+func WithOnInit(init ...func(ctx Context)) PropsOption {
+ return func(props *Props) {
+ props.onInit = append(props.onInit, init...)
+ }
+}
+
+func WithProducer(p Producer) PropsOption {
+ return func(props *Props) {
+ props.producer = p
+ }
+}
+
+func WithDispatcher(dispatcher Dispatcher) PropsOption {
+ return func(props *Props) {
+ props.dispatcher = dispatcher
+ }
+}
+
+func WithMailbox(mailbox MailboxProducer) PropsOption {
+ return func(props *Props) {
+ props.mailboxProducer = mailbox
+ }
+}
+
+func WithContextDecorator(contextDecorator ...ContextDecorator) PropsOption {
+ return func(props *Props) {
+ props.contextDecorator = append(props.contextDecorator, contextDecorator...)
+
+ props.contextDecoratorChain = makeContextDecoratorChain(props.contextDecorator, func(ctx Context) Context {
+ return ctx
+ })
+ }
+}
+
+func WithGuardian(guardian SupervisorStrategy) PropsOption {
+ return func(props *Props) {
+ props.guardianStrategy = guardian
+ }
+}
+
+func WithSupervisor(supervisor SupervisorStrategy) PropsOption {
+ return func(props *Props) {
+ props.supervisionStrategy = supervisor
+ }
+}
+
+func WithReceiverMiddleware(middleware ...ReceiverMiddleware) PropsOption {
+ return func(props *Props) {
+ props.receiverMiddleware = append(props.receiverMiddleware, middleware...)
+
+ // Construct the receiver middleware chain with the final receiver at the end
+ props.receiverMiddlewareChain = makeReceiverMiddlewareChain(props.receiverMiddleware, func(ctx ReceiverContext, envelope *MessageEnvelope) {
+ ctx.Receive(envelope)
+ })
+ }
+}
+
+func WithSenderMiddleware(middleware ...SenderMiddleware) PropsOption {
+ return func(props *Props) {
+ props.senderMiddleware = append(props.senderMiddleware, middleware...)
+
+ // Construct the sender middleware chain with the final sender at the end
+ props.senderMiddlewareChain = makeSenderMiddlewareChain(props.senderMiddleware, func(sender SenderContext, target *PID, envelope *MessageEnvelope) {
+ target.sendUserMessage(sender.ActorSystem(), envelope)
+ })
+ }
+}
+
+func WithSpawnFunc(spawn SpawnFunc) PropsOption {
+ return func(props *Props) {
+ props.spawner = spawn
+ }
+}
+
+func WithFunc(f ReceiveFunc) PropsOption {
+ return func(props *Props) {
+ props.producer = func() Actor { return f }
+ }
+}
+
+func WithSpawnMiddleware(middleware ...SpawnMiddleware) PropsOption {
+ return func(props *Props) {
+ props.spawnMiddleware = append(props.spawnMiddleware, middleware...)
+
+ // Construct the spawner middleware chain with the final spawner at the end
+ props.spawnMiddlewareChain = makeSpawnMiddlewareChain(props.spawnMiddleware, func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (pid *PID, e error) {
+ if props.spawner == nil {
+ return defaultSpawner(actorSystem, id, props, parentContext)
+ }
+
+ return props.spawner(actorSystem, id, props, parentContext)
+ })
+ }
+}
+
+// PropsFromProducer creates a props with the given actor producer assigned.
+func PropsFromProducer(producer Producer, opts ...PropsOption) *Props {
+ p := &Props{
+ producer: producer,
+ contextDecorator: make([]ContextDecorator, 0),
+ }
+ p.Configure(opts...)
+
+ return p
+}
+
+// PropsFromFunc creates a props with the given receive func assigned as the actor producer.
+func PropsFromFunc(f ReceiveFunc, opts ...PropsOption) *Props {
+ p := PropsFromProducer(func() Actor { return f }, opts...)
+
+ return p
+}
+
+func (props *Props) Clone(opts ...PropsOption) *Props {
+ cp := PropsFromProducer(props.producer,
+ WithDispatcher(props.dispatcher),
+ WithMailbox(props.mailboxProducer),
+ WithContextDecorator(props.contextDecorator...),
+ WithGuardian(props.guardianStrategy),
+ WithSupervisor(props.supervisionStrategy),
+ WithReceiverMiddleware(props.receiverMiddleware...),
+ WithSenderMiddleware(props.senderMiddleware...),
+ WithSpawnFunc(props.spawner),
+ WithSpawnMiddleware(props.spawnMiddleware...),
+ WithOnInit(props.onInit...),
+ )
+
+ cp.Configure(opts...)
+
+ return cp
+}
diff --git a/actor/props_test.go b/actor/props_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5083be6a5f9f2d54f9500072060725225218769f
--- /dev/null
+++ b/actor/props_test.go
@@ -0,0 +1,11 @@
+package actor
+
+import "testing"
+
+func TestProps_Clone(t *testing.T) {
+ p := PropsFromFunc(func(c Context) {}, WithOnInit(func(c Context) {}))
+ p2 := p.Clone()
+ if p == p2 {
+ t.Error("Clone should return a new instance")
+ }
+}
diff --git a/actor/queue.go b/actor/queue.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdfa616502c708a3fe8f962f7ae1d17afe7f5248
--- /dev/null
+++ b/actor/queue.go
@@ -0,0 +1,6 @@
+package actor
+
+type queue interface {
+ Push(interface{})
+ Pop() interface{}
+}
diff --git a/actor/root_context.go b/actor/root_context.go
new file mode 100644
index 0000000000000000000000000000000000000000..aae7ba718a59091a30fc8bf3c1f344821ee99c11
--- /dev/null
+++ b/actor/root_context.go
@@ -0,0 +1,221 @@
+package actor
+
+import (
+ "time"
+)
+
+type RootContext struct {
+ actorSystem *ActorSystem
+ senderMiddleware SenderFunc
+ spawnMiddleware SpawnFunc
+ headers messageHeader
+ guardianStrategy SupervisorStrategy
+}
+
+var (
+ _ SenderContext = &RootContext{}
+ _ SpawnerContext = &RootContext{}
+ _ stopperPart = &RootContext{}
+)
+
+func NewRootContext(actorSystem *ActorSystem, header map[string]string, middleware ...SenderMiddleware) *RootContext {
+ if header == nil {
+ header = make(map[string]string)
+ }
+
+ return &RootContext{
+ actorSystem: actorSystem,
+ senderMiddleware: makeSenderMiddlewareChain(middleware, func(_ SenderContext, target *PID, envelope *MessageEnvelope) {
+ target.sendUserMessage(actorSystem, envelope)
+ }),
+ headers: header,
+ }
+}
+
+func (rc RootContext) Copy() *RootContext {
+ return &rc
+}
+
+func (rc *RootContext) ActorSystem() *ActorSystem {
+ return rc.actorSystem
+}
+
+func (rc *RootContext) WithHeaders(headers map[string]string) *RootContext {
+ rc.headers = headers
+
+ return rc
+}
+
+func (rc *RootContext) WithSenderMiddleware(middleware ...SenderMiddleware) *RootContext {
+ rc.senderMiddleware = makeSenderMiddlewareChain(middleware, func(_ SenderContext, target *PID, envelope *MessageEnvelope) {
+ target.sendUserMessage(rc.actorSystem, envelope)
+ })
+
+ return rc
+}
+
+func (rc *RootContext) WithSpawnMiddleware(middleware ...SpawnMiddleware) *RootContext {
+ rc.spawnMiddleware = makeSpawnMiddlewareChain(middleware, func(actorSystem *ActorSystem, id string, props *Props, parentContext SpawnerContext) (pid *PID, e error) {
+ return props.spawn(actorSystem, id, rc)
+ })
+
+ return rc
+}
+
+func (rc *RootContext) WithGuardian(guardian SupervisorStrategy) *RootContext {
+ rc.guardianStrategy = guardian
+
+ return rc
+}
+
+//
+// Interface: info
+//
+
+func (rc *RootContext) Parent() *PID {
+ return nil
+}
+
+func (rc *RootContext) Self() *PID {
+ if rc.guardianStrategy != nil {
+ return rc.actorSystem.Guardians.getGuardianPid(rc.guardianStrategy)
+ }
+
+ return nil
+}
+
+func (rc *RootContext) Sender() *PID {
+ return nil
+}
+
+func (rc *RootContext) Actor() Actor {
+ return nil
+}
+
+//
+// Interface: sender
+//
+
+func (rc *RootContext) Message() interface{} {
+ return nil
+}
+
+func (rc *RootContext) MessageHeader() ReadonlyMessageHeader {
+ return rc.headers
+}
+
+func (rc *RootContext) Send(pid *PID, message interface{}) {
+ rc.sendUserMessage(pid, message)
+}
+
+func (rc *RootContext) Request(pid *PID, message interface{}) {
+ rc.sendUserMessage(pid, message)
+}
+
+func (rc *RootContext) RequestWithCustomSender(pid *PID, message interface{}, sender *PID) {
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: sender,
+ }
+ rc.sendUserMessage(pid, env)
+}
+
+// RequestFuture sends a message to a given PID and returns a Future.
+func (rc *RootContext) RequestFuture(pid *PID, message interface{}, timeout time.Duration) *Future {
+ future := NewFuture(rc.actorSystem, timeout)
+ env := &MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: future.PID(),
+ }
+ rc.sendUserMessage(pid, env)
+
+ return future
+}
+
+func (rc *RootContext) sendUserMessage(pid *PID, message interface{}) {
+ if rc.senderMiddleware != nil {
+ // Request based middleware
+ rc.senderMiddleware(rc, pid, WrapEnvelope(message))
+ } else {
+ // tell based middleware
+ pid.sendUserMessage(rc.actorSystem, message)
+ }
+}
+
+//
+// Interface: spawner
+//
+
+// Spawn starts a new actor based on props and named with a unique id.
+func (rc *RootContext) Spawn(props *Props) *PID {
+ pid, err := rc.SpawnNamed(props, rc.actorSystem.ProcessRegistry.NextId())
+ if err != nil {
+ panic(err)
+ }
+
+ return pid
+}
+
+// SpawnPrefix starts a new actor based on props and named using a prefix followed by a unique id.
+func (rc *RootContext) SpawnPrefix(props *Props, prefix string) *PID {
+ pid, err := rc.SpawnNamed(props, prefix+rc.actorSystem.ProcessRegistry.NextId())
+ if err != nil {
+ panic(err)
+ }
+
+ return pid
+}
+
+// SpawnNamed starts a new actor based on props and named using the specified name
+//
+// # ErrNameExists will be returned if id already exists
+//
+// Please do not use name sharing same pattern with system actors, for example "YourPrefix$1", "Remote$1", "future$1".
+func (rc *RootContext) SpawnNamed(props *Props, name string) (*PID, error) {
+ rootContext := rc
+ if props.guardianStrategy != nil {
+ rootContext = rc.Copy().WithGuardian(props.guardianStrategy)
+ }
+
+ if rootContext.spawnMiddleware != nil {
+ return rc.spawnMiddleware(rc.actorSystem, name, props, rootContext)
+ }
+
+ return props.spawn(rc.actorSystem, name, rootContext)
+}
+
+//
+// Interface: StopperContext
+//
+
+// Stop will stop actor immediately regardless of existing user messages in mailbox.
+func (rc *RootContext) Stop(pid *PID) {
+ pid.ref(rc.actorSystem).Stop(pid)
+}
+
+// StopFuture will stop actor immediately regardless of existing user messages in mailbox, and return its future.
+func (rc *RootContext) StopFuture(pid *PID) *Future {
+ future := NewFuture(rc.actorSystem, 10*time.Second)
+
+ pid.sendSystemMessage(rc.actorSystem, &Watch{Watcher: future.pid})
+ rc.Stop(pid)
+
+ return future
+}
+
+// Poison will tell actor to stop after processing current user messages in mailbox.
+func (rc *RootContext) Poison(pid *PID) {
+ pid.sendUserMessage(rc.actorSystem, poisonPillMessage)
+}
+
+// PoisonFuture will tell actor to stop after processing current user messages in mailbox, and return its future.
+func (rc *RootContext) PoisonFuture(pid *PID) *Future {
+ future := NewFuture(rc.actorSystem, 10*time.Second)
+
+ pid.sendSystemMessage(rc.actorSystem, &Watch{Watcher: future.pid})
+ rc.Poison(pid)
+
+ return future
+}
diff --git a/actor/spawn_example_test.go b/actor/spawn_example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d1d880abad4a0f7ae870cab48212f393fab9c99
--- /dev/null
+++ b/actor/spawn_example_test.go
@@ -0,0 +1,33 @@
+package actor_test
+
+import (
+ "fmt"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Spawn creates instances of actors, similar to 'new' or 'make' but for actors.
+func ExampleRootContext_Spawn() {
+ var wg sync.WaitGroup
+ wg.Add(1)
+
+ // create root context
+ // define the actor props.
+ // props define the creation process of an actor
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ // check if the message is a *actor.Started message
+ // this is the first message all actors get
+ // actor.Started is received async and can be used
+ // to initialize your actors initial state
+ if _, ok := ctx.Message().(*actor.Started); ok {
+ fmt.Println("hello world")
+ wg.Done()
+ }
+ })
+
+ // spawn the actor based on the props
+ system.Root.Spawn(props)
+ wg.Wait()
+ // Output: hello world
+}
diff --git a/actor/spawn_named_example_test.go b/actor/spawn_named_example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c45d92cdcdd2c831d4818e85cedd71af31d910e2
--- /dev/null
+++ b/actor/spawn_named_example_test.go
@@ -0,0 +1,39 @@
+package actor_test
+
+import (
+ "fmt"
+ "log"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Spawn creates instances of actors, similar to 'new' or 'make' but for actors.
+func ExampleRootContext_SpawnNamed() {
+ var wg sync.WaitGroup
+ wg.Add(1)
+
+ // create root context
+ context := system.Root
+
+ // define the actor props.
+ // props define the creation process of an actor
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ // check if the message is a *actor.Started message
+ // this is the first message all actors get
+ // actor.Started is received async and can be used
+ // to initialize your actors initial state
+ if _, ok := ctx.Message().(*actor.Started); ok {
+ fmt.Println("hello world")
+ wg.Done()
+ }
+ })
+
+ // spawn the actor based on the props
+ _, err := context.SpawnNamed(props, "my-actor")
+ if err != nil {
+ log.Fatal("The actor name is already in use")
+ }
+ wg.Wait()
+ // Output: hello world
+}
diff --git a/actor/spawn_test.go b/actor/spawn_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b10f460d7bc5e7c9ebeaf5fc7842fa72646927fe
--- /dev/null
+++ b/actor/spawn_test.go
@@ -0,0 +1,59 @@
+package actor
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type Increment struct{}
+
+type GorgeousActor struct {
+ Counter
+}
+
+type Counter struct {
+ value int
+}
+
+func (counter *Counter) Increment() {
+ counter.value = counter.value + 1
+}
+
+func (a *GorgeousActor) Receive(context Context) {
+ switch context.Message().(type) {
+ case *Started:
+ case Increment:
+ a.Increment()
+ context.Respond(a.value)
+ }
+}
+
+func TestLookupById(t *testing.T) {
+ ID := "UniqueID"
+ {
+ props := PropsFromProducer(func() Actor { return &GorgeousActor{Counter: Counter{value: 0}} })
+ pid, _ := rootContext.SpawnNamed(props, ID)
+ defer rootContext.Stop(pid)
+
+ result := rootContext.RequestFuture(pid, Increment{}, testTimeout)
+ value, err := result.Result()
+ if err != nil {
+ assert.Fail(t, "timed out")
+ return
+ }
+ assert.IsType(t, 0, value)
+ assert.Equal(t, 1, value.(int))
+ }
+ {
+ props := PropsFromProducer(func() Actor { return &GorgeousActor{Counter: Counter{value: 0}} })
+ pid, _ := rootContext.SpawnNamed(props, ID)
+ result := rootContext.RequestFuture(pid, Increment{}, testTimeout)
+ value, err := result.Result()
+ if err != nil {
+ assert.Fail(t, "timed out")
+ return
+ }
+ assert.Equal(t, 2, value.(int))
+ }
+}
diff --git a/actor/strategy_all_for_one.go b/actor/strategy_all_for_one.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f4b58b0b3a2e9553c62f198c293b319d2fd6ed7
--- /dev/null
+++ b/actor/strategy_all_for_one.go
@@ -0,0 +1,70 @@
+package actor
+
+import "time"
+
+// NewAllForOneStrategy returns a new SupervisorStrategy which applies the given fault Directive from the decider to the
+// failing child and all its children.
+//
+// This strategy is appropriate when the children have a strong dependency, such that and any single one failing would
+// place them all into a potentially invalid state.
+func NewAllForOneStrategy(maxNrOfRetries int, withinDuration time.Duration, decider DeciderFunc) SupervisorStrategy {
+ return &allForOneStrategy{
+ maxNrOfRetries: maxNrOfRetries,
+ withinDuration: withinDuration,
+ decider: decider,
+ }
+}
+
+type allForOneStrategy struct {
+ maxNrOfRetries int
+ withinDuration time.Duration
+ decider DeciderFunc
+}
+
+var _ SupervisorStrategy = &allForOneStrategy{}
+
+func (strategy *allForOneStrategy) HandleFailure(actorSystem *ActorSystem, supervisor Supervisor, child *PID, rs *RestartStatistics, reason interface{}, message interface{}) {
+ directive := strategy.decider(reason)
+ switch directive {
+ case ResumeDirective:
+ // resume the failing child
+ logFailure(actorSystem, child, reason, directive)
+ supervisor.ResumeChildren(child)
+ case RestartDirective:
+ children := supervisor.Children()
+ // try restart the all the children
+ if strategy.shouldStop(rs) {
+ logFailure(actorSystem, child, reason, StopDirective)
+ supervisor.StopChildren(children...)
+ } else {
+ logFailure(actorSystem, child, reason, RestartDirective)
+ supervisor.RestartChildren(children...)
+ }
+ case StopDirective:
+ children := supervisor.Children()
+ // stop all the children, no need to involve the crs
+ logFailure(actorSystem, child, reason, directive)
+ supervisor.StopChildren(children...)
+ case EscalateDirective:
+ // send failure to parent
+ // supervisor mailbox
+ // do not log here, log in the parent handling the error
+ supervisor.EscalateFailure(reason, message)
+ }
+}
+
+func (strategy *allForOneStrategy) shouldStop(rs *RestartStatistics) bool {
+ // supervisor says this child may not restart
+ if strategy.maxNrOfRetries == 0 {
+ return true
+ }
+
+ rs.Fail()
+
+ if rs.NumberOfFailures(strategy.withinDuration) > strategy.maxNrOfRetries {
+ rs.Reset()
+ return true
+ }
+
+ return false
+}
diff --git a/actor/strategy_exponential_backoff.go b/actor/strategy_exponential_backoff.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7029388898208aa52390bd699126a1845576227
--- /dev/null
+++ b/actor/strategy_exponential_backoff.go
@@ -0,0 +1,44 @@
+package actor
+
+import (
+ "math/rand"
+ "time"
+)
+
+// NewExponentialBackoffStrategy creates a new Supervisor strategy that restarts a faulting child using an exponential
+// back off algorithm:
+//
+// delay =
+func NewExponentialBackoffStrategy(backoffWindow time.Duration, initialBackoff time.Duration) SupervisorStrategy {
+ return &exponentialBackoffStrategy{
+ backoffWindow: backoffWindow,
+ initialBackoff: initialBackoff,
+ }
+}
+
+type exponentialBackoffStrategy struct {
+ backoffWindow time.Duration
+ initialBackoff time.Duration
+}
+
+var _ SupervisorStrategy = &exponentialBackoffStrategy{}
+
+func (strategy *exponentialBackoffStrategy) HandleFailure(actorSystem *ActorSystem, supervisor Supervisor, child *PID, rs *RestartStatistics, reason interface{}, _ interface{}) {
+ strategy.setFailureCount(rs)
+
+ backoff := rs.FailureCount() * int(strategy.initialBackoff.Nanoseconds())
+ noise := rand.Intn(500)
+ dur := time.Duration(backoff + noise)
+ time.AfterFunc(dur, func() {
+ logFailure(actorSystem, child, reason, RestartDirective)
+ supervisor.RestartChildren(child)
+ })
+}
+
+func (strategy *exponentialBackoffStrategy) setFailureCount(rs *RestartStatistics) {
+ if rs.NumberOfFailures(strategy.backoffWindow) == 0 {
+ rs.Reset()
+ }
+
+ rs.Fail()
+}
diff --git a/actor/strategy_exponential_backoff_test.go b/actor/strategy_exponential_backoff_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bb4d68fda5c9b6587cdf55d6d3e38e55c6e3dc9c
--- /dev/null
+++ b/actor/strategy_exponential_backoff_test.go
@@ -0,0 +1,56 @@
+package actor
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestExponentialBackoffStrategy_setFailureCount(t *testing.T) {
+ cases := []struct {
+ n string
+ ft time.Duration
+ fc int
+ expected int
+ }{
+ {n: "failure outside window; increment count", ft: 11 * time.Second, fc: 10, expected: 1},
+ {n: "failure inside window; increment count", ft: 9 * time.Second, fc: 10, expected: 11},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.n, func(t *testing.T) {
+ s := &exponentialBackoffStrategy{backoffWindow: 10 * time.Second}
+ rs := &RestartStatistics{[]time.Time{}}
+ for i := 0; i < tc.fc; i++ {
+ rs.failureTimes = append(rs.failureTimes, time.Now().Add(-tc.ft))
+ }
+
+ s.setFailureCount(rs)
+ assert.Equal(t, tc.expected, rs.FailureCount())
+ })
+ }
+}
+
+func TestExponentialBackoffStrategy_IncrementsFailureCount(t *testing.T) {
+ rs := NewRestartStatistics()
+ s := &exponentialBackoffStrategy{backoffWindow: 10 * time.Second}
+
+ s.setFailureCount(rs)
+ s.setFailureCount(rs)
+ s.setFailureCount(rs)
+
+ assert.Equal(t, 3, rs.FailureCount())
+}
+
+func TestExponentialBackoffStrategy_ResetsFailureCount(t *testing.T) {
+ rs := NewRestartStatistics()
+ for i := 0; i < 10; i++ {
+ rs.failureTimes = append(rs.failureTimes, time.Now().Add(-11*time.Second))
+ }
+ s := &exponentialBackoffStrategy{backoffWindow: 10 * time.Second, initialBackoff: 1 * time.Second}
+
+ s.setFailureCount(rs)
+
+ assert.Equal(t, 1, rs.FailureCount())
+}
diff --git a/actor/strategy_one_for_one.go b/actor/strategy_one_for_one.go
new file mode 100644
index 0000000000000000000000000000000000000000..45d7749102191019dd8de45084eee1d591e4374a
--- /dev/null
+++ b/actor/strategy_one_for_one.go
@@ -0,0 +1,68 @@
+package actor
+
+import "time"
+
+// NewOneForOneStrategy returns a new Supervisor strategy which applies the fault Directive from the decider
+// to the failing child process.
+//
+// This strategy is applicable if it is safe to handle a single child in isolation from its peers or dependents
+func NewOneForOneStrategy(maxNrOfRetries int, withinDuration time.Duration, decider DeciderFunc) SupervisorStrategy {
+ return &oneForOneStrategy{
+ maxNrOfRetries: maxNrOfRetries,
+ withinDuration: withinDuration,
+ decider: decider,
+ }
+}
+
+type oneForOneStrategy struct {
+ maxNrOfRetries int
+ withinDuration time.Duration
+ decider DeciderFunc
+}
+
+var _ SupervisorStrategy = &oneForOneStrategy{}
+
+func (strategy *oneForOneStrategy) HandleFailure(actorSystem *ActorSystem, supervisor Supervisor, child *PID, rs *RestartStatistics, reason interface{}, message interface{}) {
+ directive := strategy.decider(reason)
+
+ switch directive {
+ case ResumeDirective:
+ // resume the failing child
+ logFailure(actorSystem, child, reason, directive)
+ supervisor.ResumeChildren(child)
+ case RestartDirective:
+ // try restart the failing child
+ if strategy.shouldStop(rs) {
+ logFailure(actorSystem, child, reason, StopDirective)
+ supervisor.StopChildren(child)
+ } else {
+ logFailure(actorSystem, child, reason, RestartDirective)
+ supervisor.RestartChildren(child)
+ }
+ case StopDirective:
+ // stop the failing child, no need to involve the crs
+ logFailure(actorSystem, child, reason, directive)
+ supervisor.StopChildren(child)
+ case EscalateDirective:
+ // send failure to parent
+ // supervisor mailbox
+ // do not log here, log in the parent handling the error
+ supervisor.EscalateFailure(reason, message)
+ }
+}
+
+func (strategy *oneForOneStrategy) shouldStop(rs *RestartStatistics) bool {
+ // supervisor says this child may not restart
+ if strategy.maxNrOfRetries == 0 {
+ return true
+ }
+
+ rs.Fail()
+
+ if rs.NumberOfFailures(strategy.withinDuration) > strategy.maxNrOfRetries {
+ rs.Reset()
+ return true
+ }
+
+ return false
+}
diff --git a/actor/strategy_one_for_one_test.go b/actor/strategy_one_for_one_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ad0e718c192538f1e030f50af84ebd7060e4150
--- /dev/null
+++ b/actor/strategy_one_for_one_test.go
@@ -0,0 +1,83 @@
+package actor
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestOneForOneStrategy_requestRestartPermission(t *testing.T) {
+ cases := []struct {
+ n string
+ expectedResult bool
+ expectedCount int
+ s oneForOneStrategy
+ rs RestartStatistics
+ }{
+ {
+ n: "no restart if max retries is 0",
+
+ s: oneForOneStrategy{maxNrOfRetries: 0},
+ rs: RestartStatistics{},
+
+ expectedResult: true,
+ expectedCount: 0,
+ },
+ {
+ n: "restart when duration is 0",
+
+ s: oneForOneStrategy{maxNrOfRetries: 1},
+ rs: RestartStatistics{},
+
+ expectedResult: false,
+ expectedCount: 1,
+ },
+ {
+ n: "no restart when duration is 0 and exceeds max retries",
+
+ s: oneForOneStrategy{maxNrOfRetries: 1},
+ rs: RestartStatistics{failureTimes: []time.Time{time.Now().Add(-1 * time.Second)}},
+
+ expectedResult: true,
+ expectedCount: 0,
+ },
+ {
+ n: "restart when duration set and within window",
+
+ s: oneForOneStrategy{maxNrOfRetries: 2, withinDuration: 10 * time.Second},
+ rs: RestartStatistics{failureTimes: []time.Time{time.Now().Add(-5 * time.Second)}},
+
+ expectedResult: false,
+ expectedCount: 2,
+ },
+ {
+ n: "no restart when duration set, within window and exceeds max retries",
+
+ s: oneForOneStrategy{maxNrOfRetries: 1, withinDuration: 10 * time.Second},
+ rs: RestartStatistics{failureTimes: []time.Time{time.Now().Add(-5 * time.Second), time.Now().Add(-5 * time.Second)}},
+
+ expectedResult: true,
+ expectedCount: 0,
+ },
+ {
+ n: "restart and FailureCount reset when duration set and outside window",
+
+ s: oneForOneStrategy{maxNrOfRetries: 1, withinDuration: 10 * time.Second},
+ rs: RestartStatistics{failureTimes: []time.Time{time.Now().Add(-11 * time.Second), time.Now().Add(-11 * time.Second)}},
+
+ expectedResult: false,
+ expectedCount: 1,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.n, func(t *testing.T) {
+ s := tc.s
+ rs := tc.rs
+ actual := s.shouldStop(&rs)
+ assert.Equal(t, tc.expectedResult, actual)
+ assert.Equal(t, tc.expectedCount, rs.NumberOfFailures(s.withinDuration))
+ })
+ }
+}
diff --git a/actor/strategy_restarting.go b/actor/strategy_restarting.go
new file mode 100644
index 0000000000000000000000000000000000000000..f947c3fb6aa946a3fc19d31c8be05db4d7b42e7d
--- /dev/null
+++ b/actor/strategy_restarting.go
@@ -0,0 +1,15 @@
+package actor
+
+func NewRestartingStrategy() SupervisorStrategy {
+ return &restartingStrategy{}
+}
+
+type restartingStrategy struct{}
+
+var _ SupervisorStrategy = &restartingStrategy{}
+
+func (strategy *restartingStrategy) HandleFailure(actorSystem *ActorSystem, supervisor Supervisor, child *PID, _ *RestartStatistics, reason interface{}, _ interface{}) {
+ // always restart
+ logFailure(actorSystem, child, reason, RestartDirective)
+ supervisor.RestartChildren(child)
+}
diff --git a/actor/supervision.go b/actor/supervision.go
new file mode 100644
index 0000000000000000000000000000000000000000..a858c4a6e40a29f24ea085095ec61b5d6714bde3
--- /dev/null
+++ b/actor/supervision.go
@@ -0,0 +1,48 @@
+package actor
+
+import (
+ "time"
+)
+
+// DeciderFunc is a function which is called by a SupervisorStrategy
+type DeciderFunc func(reason interface{}) Directive
+
+// SupervisorStrategy is an interface that decides how to handle failing child actors
+type SupervisorStrategy interface {
+ HandleFailure(actorSystem *ActorSystem, supervisor Supervisor, child *PID, rs *RestartStatistics, reason interface{}, message interface{})
+}
+
+// Supervisor is an interface that is used by the SupervisorStrategy to manage child actor lifecycle
+type Supervisor interface {
+ Children() []*PID
+ EscalateFailure(reason interface{}, message interface{})
+ RestartChildren(pids ...*PID)
+ StopChildren(pids ...*PID)
+ ResumeChildren(pids ...*PID)
+}
+
+func logFailure(actorSystem *ActorSystem, child *PID, reason interface{}, directive Directive) {
+ actorSystem.EventStream.Publish(&SupervisorEvent{
+ Child: child,
+ Reason: reason,
+ Directive: directive,
+ })
+}
+
+// DefaultDecider is a decider that will always restart the failing child actor
+func DefaultDecider(_ interface{}) Directive {
+ return RestartDirective
+}
+
+var (
+ defaultSupervisionStrategy = NewOneForOneStrategy(10, 10*time.Second, DefaultDecider)
+ restartingSupervisionStrategy = NewRestartingStrategy()
+)
+
+func DefaultSupervisorStrategy() SupervisorStrategy {
+ return defaultSupervisionStrategy
+}
+
+func RestartingSupervisorStrategy() SupervisorStrategy {
+ return restartingSupervisionStrategy
+}
diff --git a/actor/supervision_event.go b/actor/supervision_event.go
new file mode 100644
index 0000000000000000000000000000000000000000..944b1fab35f77e9c9f929c8da21d569562eac66e
--- /dev/null
+++ b/actor/supervision_event.go
@@ -0,0 +1,20 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+// SupervisorEvent is sent on the EventStream when a supervisor have applied a directive to a failing child actor
+type SupervisorEvent struct {
+ Child *PID
+ Reason interface{}
+ Directive Directive
+}
+
+func SubscribeSupervision(actorSystem *ActorSystem) {
+ _ = actorSystem.EventStream.Subscribe(func(evt interface{}) {
+ if supervisorEvent, ok := evt.(*SupervisorEvent); ok {
+ plog.Debug("[SUPERVISION]", log.Stringer("actor", supervisorEvent.Child), log.Stringer("directive", supervisorEvent.Directive), log.Object("reason", supervisorEvent.Reason))
+ }
+ })
+}
diff --git a/actor/supervision_event_test.go b/actor/supervision_event_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a3108cac2cfb10d099708db73499ce0b1b5d2fee
--- /dev/null
+++ b/actor/supervision_event_test.go
@@ -0,0 +1,60 @@
+package actor
+
+import (
+ "sync"
+ "testing"
+ "time"
+)
+
+type panicActor struct{}
+
+func (a *panicActor) Receive(ctx Context) {
+ switch ctx.Message().(type) {
+ case string:
+ panic("Boom!")
+ }
+}
+
+func TestSupervisorEventHandleFromEventstream(t *testing.T) {
+ supervisors := []struct {
+ name string
+ strategy SupervisorStrategy
+ }{
+ {
+ name: "all_for_one",
+ strategy: NewAllForOneStrategy(10, 10*time.Second, DefaultDecider),
+ },
+ {
+ name: "exponential_backoff",
+ strategy: NewExponentialBackoffStrategy(10*time.Millisecond, 10*time.Millisecond),
+ },
+ {
+ name: "one_for_one",
+ strategy: NewOneForOneStrategy(10, 10*time.Second, DefaultDecider),
+ },
+ {
+ name: "restarting",
+ strategy: NewRestartingStrategy(),
+ },
+ }
+
+ for _, v := range supervisors {
+ t.Run(v.name, func(t *testing.T) {
+ wg := sync.WaitGroup{}
+ sid := system.EventStream.Subscribe(func(evt interface{}) {
+ if _, ok := evt.(*SupervisorEvent); ok {
+ wg.Done()
+ }
+ })
+ defer system.EventStream.Unsubscribe(sid)
+
+ props := PropsFromProducer(func() Actor { return &panicActor{} }, WithSupervisor(v.strategy))
+ pid := rootContext.Spawn(props)
+
+ wg.Add(1)
+ rootContext.Send(pid, "Fail!")
+
+ wg.Wait()
+ })
+ }
+}
diff --git a/actor/supervision_test.go b/actor/supervision_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6ead7b0dee81dec4939442ad130dc551d8bf23f5
--- /dev/null
+++ b/actor/supervision_test.go
@@ -0,0 +1,102 @@
+package actor
+
+import (
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+)
+
+type actorWithSupervisor struct {
+ wg *sync.WaitGroup
+}
+
+func (a *actorWithSupervisor) Receive(ctx Context) {
+ switch ctx.Message().(type) {
+ case *Started:
+ child := ctx.Spawn(PropsFromProducer(func() Actor { return &failingChildActor{} }))
+ ctx.Send(child, "Fail!")
+ }
+}
+
+func (a *actorWithSupervisor) HandleFailure(*ActorSystem, Supervisor, *PID, *RestartStatistics, interface{}, interface{}) {
+ a.wg.Done()
+}
+
+type failingChildActor struct{}
+
+func (a *failingChildActor) Receive(ctx Context) {
+ switch ctx.Message().(type) {
+ case string:
+ panic("Oh noes!")
+ }
+}
+
+func TestActorWithOwnSupervisorCanHandleFailure(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ props := PropsFromProducer(func() Actor { return &actorWithSupervisor{wg: wg} })
+ rootContext.Spawn(props)
+ wg.Wait()
+}
+
+func NewObserver() (func(ReceiverFunc) ReceiverFunc, *Expector) {
+ c := make(chan interface{})
+ e := &Expector{C: c}
+ f := func(next ReceiverFunc) ReceiverFunc {
+ fn := func(context ReceiverContext, env *MessageEnvelope) {
+ message := env.Message
+ c <- message
+ next(context, env)
+ }
+
+ return fn
+ }
+ return f, e
+}
+
+type Expector struct {
+ C <-chan interface{}
+}
+
+func (e *Expector) ExpectMsg(expected interface{}, t *testing.T) {
+ actual := <-e.C
+ if actual == expected {
+ } else {
+
+ at := reflect.TypeOf(actual)
+ et := reflect.TypeOf(expected)
+ t.Errorf("Expected %v:%v, got %v:%v", et, expected, at, actual)
+ }
+}
+
+func (e *Expector) ExpectNoMsg(t *testing.T) {
+ select {
+ case actual := <-e.C:
+ at := reflect.TypeOf(actual)
+ t.Errorf("Expected no message got %v:%v", at, actual)
+ case <-time.After(time.Second * 1):
+ // pass
+ }
+}
+
+func TestActorStopsAfterXRestarts(t *testing.T) {
+ m, e := NewObserver()
+ props := PropsFromProducer(func() Actor { return &failingChildActor{} }, WithReceiverMiddleware(m))
+ child := rootContext.Spawn(props)
+ fail := "fail!"
+
+ e.ExpectMsg(startedMessage, t)
+
+ // root supervisor allows 10 restarts
+ for i := 0; i < 10; i++ {
+ rootContext.Send(child, fail)
+ e.ExpectMsg(fail, t)
+ e.ExpectMsg(restartingMessage, t)
+ e.ExpectMsg(startedMessage, t)
+ }
+ rootContext.Send(child, fail)
+ e.ExpectMsg(fail, t)
+ // the 11th time should cause a termination
+ e.ExpectMsg(stoppingMessage, t)
+}
diff --git a/actor/throttler.go b/actor/throttler.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d0e9cf0a31a0222621a42e27b209e86376e1912
--- /dev/null
+++ b/actor/throttler.go
@@ -0,0 +1,55 @@
+package actor
+
+import (
+ "sync/atomic"
+ "time"
+)
+
+type ShouldThrottle func() Valve
+
+type Valve int32
+
+const (
+ Open Valve = iota
+ Closing
+ Closed
+)
+
+// NewThrottle
+// This has no guarantees that the throttle opens exactly after the period, since it is reset asynchronously
+// Throughput has been prioritized over exact re-opening
+// throttledCallBack, This will be called with the number of events what was throttled after the period
+func NewThrottle(maxEventsInPeriod int32, period time.Duration, throttledCallBack func(int32)) ShouldThrottle {
+ currentEvents := int32(0)
+
+ startTimer := func(duration time.Duration, back func(int32)) {
+ go func() {
+ // crete ticker to mimic sleep, we do not want to put the goroutine to sleep
+ // as it will schedule it out of the P making a syscall, we just want it to
+ // halt for the given period of time
+ ticker := time.NewTicker(duration)
+ defer ticker.Stop()
+ <-ticker.C // wait for the ticker to tick once
+
+ timesCalled := atomic.SwapInt32(¤tEvents, 0)
+ if timesCalled > maxEventsInPeriod {
+ throttledCallBack(timesCalled - maxEventsInPeriod)
+ }
+ }()
+ }
+
+ return func() Valve {
+ tries := atomic.AddInt32(¤tEvents, 1)
+ if tries == 1 {
+ startTimer(period, throttledCallBack)
+ }
+
+ if tries == maxEventsInPeriod {
+ return Closing
+ } else if tries > maxEventsInPeriod {
+ return Closed
+ } else {
+ return Open
+ }
+ }
+}
diff --git a/actor/throttler_test.go b/actor/throttler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..161613825af9d62ac3baef56b15a9d6b17c6a40a
--- /dev/null
+++ b/actor/throttler_test.go
@@ -0,0 +1,43 @@
+package actor
+
+import (
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestThrottler(t *testing.T) {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ // create a throttler that triggers after 10 invocations within 1 second
+ throttler := NewThrottle(10, 1*time.Second, func(i int32) {
+ wg.Done()
+ })
+
+ throttler()
+ v := throttler()
+
+ assert.Equal(t, Open, v)
+
+ for i := 0; i < 8; i++ {
+ v = throttler()
+ }
+
+ // should be closing now when we have invoked 10 times
+ assert.Equal(t, Closing, v)
+
+ // invoke once more
+ v = throttler()
+ // should bee closed, 11 invokes
+ assert.Equal(t, Closed, v)
+
+ // wait for callback to be invoked
+ wg.Wait()
+
+ // valve should be open now that time has elapsed
+ v = throttler()
+ assert.Equal(t, Open, v)
+}
diff --git a/actor/unbounded.go b/actor/unbounded.go
new file mode 100644
index 0000000000000000000000000000000000000000..f91226442404b73321dec28566b774f4934c25e4
--- /dev/null
+++ b/actor/unbounded.go
@@ -0,0 +1,38 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/goring"
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+)
+
+type unboundedMailboxQueue struct {
+ userMailbox *goring.Queue
+}
+
+func (q *unboundedMailboxQueue) Push(m interface{}) {
+ q.userMailbox.Push(m)
+}
+
+func (q *unboundedMailboxQueue) Pop() interface{} {
+ m, o := q.userMailbox.Pop()
+ if o {
+ return m
+ }
+
+ return nil
+}
+
+// Unbounded returns a producer which creates an unbounded mailbox
+func Unbounded(mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return func() Mailbox {
+ q := &unboundedMailboxQueue{
+ userMailbox: goring.New(10),
+ }
+
+ return &defaultMailbox{
+ systemMailbox: mpsc.New(),
+ userMailbox: q,
+ middlewares: mailboxStats,
+ }
+ }
+}
diff --git a/actor/unbounded_lock_free.go b/actor/unbounded_lock_free.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b50b38d1bd71a59e43b0bfbf3a4fa0a5c8e38ad
--- /dev/null
+++ b/actor/unbounded_lock_free.go
@@ -0,0 +1,17 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+)
+
+// UnboundedLockfree returns a producer which creates an unbounded, lock-free mailbox.
+// This mailbox is cheaper to allocate, but has a slower throughput than the plain Unbounded mailbox.
+func UnboundedLockfree(mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return func() Mailbox {
+ return &defaultMailbox{
+ userMailbox: mpsc.New(),
+ systemMailbox: mpsc.New(),
+ middlewares: mailboxStats,
+ }
+ }
+}
diff --git a/actor/unbounded_priority.go b/actor/unbounded_priority.go
new file mode 100644
index 0000000000000000000000000000000000000000..886c43aad95d373e7470ad2450c02b887ba4ff71
--- /dev/null
+++ b/actor/unbounded_priority.go
@@ -0,0 +1,41 @@
+package actor
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/goring"
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+)
+
+func NewPriorityGoringQueue() *priorityQueue {
+ return NewPriorityQueue(func() queue {
+ return &unboundedMailboxQueue{
+ userMailbox: goring.New(10),
+ }
+ })
+}
+
+//goland:noinspection ALL
+func UnboundedPriority(mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return func() Mailbox {
+ return &defaultMailbox{
+ systemMailbox: mpsc.New(),
+ userMailbox: NewPriorityGoringQueue(),
+ middlewares: mailboxStats,
+ }
+ }
+}
+
+func NewPriorityMpscQueue() *priorityQueue {
+ return NewPriorityQueue(func() queue {
+ return mpsc.New()
+ })
+}
+
+func UnboundedPriorityMpsc(mailboxStats ...MailboxMiddleware) MailboxProducer {
+ return func() Mailbox {
+ return &defaultMailbox{
+ systemMailbox: mpsc.New(),
+ userMailbox: NewPriorityMpscQueue(),
+ middlewares: mailboxStats,
+ }
+ }
+}
diff --git a/cluster/README.MD b/cluster/README.MD
new file mode 100644
index 0000000000000000000000000000000000000000..543f658eb34e66fcc5b18c6a3e6e6567e9f41cd4
--- /dev/null
+++ b/cluster/README.MD
@@ -0,0 +1,134 @@
+# Proto.Actor Cluster - Virtual Actors (Alpha)
+
+## Massively distributed actors for GO
+
+Proto.Actor supports the classic actor model also found in Erlang and Akka.
+Our cluster support however uses a different approach, **Virtual Actor Model**.
+
+This is a model where each actor appears to *always exist*.
+There is no lifecycle as in the classic actor model.
+You get a reference to the actor by asking for it's ID.
+
+e.g.
+
+```go
+hello := shared.GetHelloGrain("abc")
+res := hello.SayHello(&shared.HelloRequest{Name: "Proto.Actor"})
+```
+
+This will ask the cluster where the 'abc' actor is located.
+If it does not yet exist, it will be created for you.
+
+See Microsoft Orleans for more info about the Virtual Actor Model:
+[http://dotnet.github.io/orleans/](http://dotnet.github.io/orleans/)
+
+## How to
+
+## Protobuf IDL Definition
+
+Start by defining your messages and grain contracts.
+You do this by using Protobuf IDL files.
+
+Here is the definition from the `/examples/cluster/shared` example
+
+```proto
+syntax = "proto3";
+package shared;
+
+message HelloRequest {
+ string name = 1;
+}
+
+message HelloResponse {
+ string message = 1;
+}
+
+message AddRequest {
+ double a = 1;
+ double b = 2;
+}
+
+message AddResponse {
+ double result = 1;
+}
+
+service Hello {
+ rpc SayHello (HelloRequest) returns (HelloResponse) {}
+ rpc Add(AddRequest) returns (AddResponse) {}
+}
+```
+
+Once you have this, you can generate your code using the protobuf `protoc` compiler.
+
+**Windows**
+
+```batch
+#generate messages
+protoc -I=. -I=%GOPATH%\src --gogoslick_out=. protos.proto
+#generate grains
+protoc -I=. -I=%GOPATH%\src --gorleans_out=. protos.proto
+```
+
+## Implementing
+
+Once the messages and contracts have been generated, you can start implementing your own business logic.
+This is essentially a type which is powered by a Proto.Actor actor behind the scenes.
+
+```go
+package shared
+
+// a Go struct implementing the Hello interface
+type hello struct {
+}
+
+func (*hello) SayHello(r *HelloRequest) *HelloResponse {
+ return &HelloResponse{Message: "hello " + r.Name}
+}
+
+func (*hello) Add(r *AddRequest) *AddResponse {
+ return &AddResponse{Result: r.A + r.B}
+}
+
+// Register what implementation Proto.Actor should use when
+// creating actors for a certain grain type.
+func init() {
+ // apply DI and setup logic
+ HelloFactory(func() Hello { return &hello{} })
+}
+```
+
+## Seed nodes
+
+```go
+func main() {
+ cluster.Start("127.0.0.1:7711")
+ console.ReadLine()
+}
+```
+
+## Member nodes
+
+```go
+func main() {
+ cluster.Start("127.0.0.1:0", "127.0.0.1:7711")
+
+ // get a reference to the virtual actor called "abc" of type Hello
+ hello := shared.GetHelloGrain("abc")
+ res := hello.SayHello(&shared.HelloRequest{Name: "Proto.Actor"})
+ log.Printf("Message from grain %v", res.Message)
+}
+```
+
+## FAQ
+
+### Can I use Proto.Actor Cluster in production?
+
+The Proto.Actor Cluster support is in alpha version, thus not production ready.
+
+### What about performance?
+
+Proto.Actor Remoting is able to pass 1 million+ messages per second on a standard dev machine.
+This is the same infrastructure used in Proto.Actor cluster.
+Proto.Actor Cluster however uses an RPC API, meaning it is Request/Response in nature.
+If you wait for a response for each call, the throughput will ofcourse be a lot less.
+Async Fire and forget for performance, Request/Response for simplicity.
diff --git a/cluster/build.sh b/cluster/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..3281957d9cbe9a83f8d18c2a0634b462a693131b
--- /dev/null
+++ b/cluster/build.sh
@@ -0,0 +1,6 @@
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --proto_path=. cluster.proto
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --proto_path=. gossip.proto
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --proto_path=. grain.proto
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --proto_path=. pubsub.proto
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --proto_path=. pubsub_test.proto
+
diff --git a/cluster/cluster.go b/cluster/cluster.go
new file mode 100644
index 0000000000000000000000000000000000000000..88c9e880dcc5c9b6f37812c80b05ea30d9e5f201
--- /dev/null
+++ b/cluster/cluster.go
@@ -0,0 +1,262 @@
+package cluster
+
+import (
+ "time"
+
+ "google.golang.org/protobuf/types/known/emptypb"
+
+ "github.com/asynkron/gofun/set"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/extensions"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+var extensionID = extensions.NextExtensionID()
+
+type Cluster struct {
+ ActorSystem *actor.ActorSystem
+ Config *Config
+ Gossip *Gossiper
+ PubSub *PubSub
+ Remote *remote.Remote
+ PidCache *PidCacheValue
+ MemberList *MemberList
+ IdentityLookup IdentityLookup
+ kinds map[string]*ActivatedKind
+ context Context
+}
+
+var _ extensions.Extension = &Cluster{}
+
+func New(actorSystem *actor.ActorSystem, config *Config) *Cluster {
+ c := &Cluster{
+ ActorSystem: actorSystem,
+ Config: config,
+ kinds: map[string]*ActivatedKind{},
+ }
+ actorSystem.Extensions.Register(c)
+
+ c.context = config.ClusterContextProducer(c)
+ c.PidCache = NewPidCache()
+ c.MemberList = NewMemberList(c)
+ c.subscribeToTopologyEvents()
+
+ actorSystem.Extensions.Register(c)
+
+ var err error
+ c.Gossip, err = newGossiper(c)
+ c.PubSub = NewPubSub(c)
+
+ if err != nil {
+ panic(err)
+ }
+
+ return c
+}
+
+func (c *Cluster) subscribeToTopologyEvents() {
+ c.ActorSystem.EventStream.Subscribe(func(evt interface{}) {
+ if clusterTopology, ok := evt.(*ClusterTopology); ok {
+ for _, member := range clusterTopology.Left {
+ c.PidCache.RemoveByMember(member)
+ }
+ }
+ })
+}
+
+func (c *Cluster) ExtensionID() extensions.ExtensionID {
+ return extensionID
+}
+
+//goland:noinspection GoUnusedExportedFunction
+func GetCluster(actorSystem *actor.ActorSystem) *Cluster {
+ c := actorSystem.Extensions.Get(extensionID)
+
+ return c.(*Cluster)
+}
+
+func (c *Cluster) GetBlockedMembers() set.Set[string] {
+ return c.Remote.BlockList().BlockedMembers()
+}
+
+func (c *Cluster) StartMember() {
+ cfg := c.Config
+ c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
+
+ c.initKinds()
+
+ // TODO: make it possible to become a cluster even if remoting is already started
+ c.Remote.Start()
+
+ address := c.ActorSystem.Address()
+ plog.Info("Starting Proto.Actor cluster member", log.String("id", c.ActorSystem.ID), log.String("address", address))
+
+ c.IdentityLookup = cfg.IdentityLookup
+ c.IdentityLookup.Setup(c, c.GetClusterKinds(), false)
+
+ // TODO: Disable Gossip for now until API changes are done
+ // gossiper must be started whenever any topology events starts flowing
+ if err := c.Gossip.StartGossiping(); err != nil {
+ panic(err)
+ }
+ c.PubSub.Start()
+ c.MemberList.InitializeTopologyConsensus()
+
+ if err := cfg.ClusterProvider.StartMember(c); err != nil {
+ panic(err)
+ }
+
+ time.Sleep(1 * time.Second)
+}
+
+func (c *Cluster) GetClusterKinds() []string {
+ keys := make([]string, 0, len(c.kinds))
+ for k := range c.kinds {
+ keys = append(keys, k)
+ }
+
+ return keys
+}
+
+func (c *Cluster) StartClient() {
+ cfg := c.Config
+ c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
+
+ c.Remote.Start()
+
+ address := c.ActorSystem.Address()
+ plog.Info("Starting Proto.Actor cluster-client", log.String("address", address))
+
+ c.IdentityLookup = cfg.IdentityLookup
+ c.IdentityLookup.Setup(c, c.GetClusterKinds(), true)
+
+ if err := cfg.ClusterProvider.StartClient(c); err != nil {
+ panic(err)
+ }
+ c.PubSub.Start()
+}
+
+func (c *Cluster) Shutdown(graceful bool) {
+ c.Gossip.SetState(GracefullyLeftKey, &emptypb.Empty{})
+ c.ActorSystem.Shutdown()
+ if graceful {
+ _ = c.Config.ClusterProvider.Shutdown(graceful)
+ c.IdentityLookup.Shutdown()
+ // This is to wait ownership transferring complete.
+ time.Sleep(time.Millisecond * 2000)
+ c.MemberList.stopMemberList()
+ c.IdentityLookup.Shutdown()
+ c.Gossip.Shutdown()
+ }
+
+ c.Remote.Shutdown(graceful)
+
+ address := c.ActorSystem.Address()
+ plog.Info("Stopped Proto.Actor cluster", log.String("address", address))
+}
+
+func (c *Cluster) Get(identity string, kind string) *actor.PID {
+ return c.IdentityLookup.Get(NewClusterIdentity(identity, kind))
+}
+
+func (c *Cluster) Request(identity string, kind string, message interface{}) (interface{}, error) {
+ return c.context.Request(identity, kind, message)
+}
+
+func (c *Cluster) GetClusterKind(kind string) *ActivatedKind {
+ k, ok := c.kinds[kind]
+ if !ok {
+ plog.Error("Invalid kind", log.String("kind", kind))
+
+ return nil
+ }
+
+ return k
+}
+
+func (c *Cluster) TryGetClusterKind(kind string) (*ActivatedKind, bool) {
+ k, ok := c.kinds[kind]
+
+ return k, ok
+}
+
+func (c *Cluster) initKinds() {
+ for name, kind := range c.Config.Kinds {
+ c.kinds[name] = kind.Build(c)
+ }
+ c.ensureTopicKindRegistered()
+}
+
+// ensureTopicKindRegistered ensures that the topic kind is registered in the cluster
+// if topic kind is not registered, it will be registered automatically
+func (c *Cluster) ensureTopicKindRegistered() {
+ hasTopicKind := false
+ for name := range c.kinds {
+ if name == TopicActorKind {
+ hasTopicKind = true
+ break
+ }
+ }
+ if !hasTopicKind {
+ store := &EmptyKeyValueStore[*Subscribers]{}
+
+ c.kinds[TopicActorKind] = NewKind(TopicActorKind, actor.PropsFromProducer(func() actor.Actor {
+ return NewTopicActor(store)
+ })).Build(c)
+ }
+}
+
+// Call is a wrap of context.RequestFuture with retries.
+func (c *Cluster) Call(name string, kind string, msg interface{}, opts ...GrainCallOption) (interface{}, error) {
+ callConfig := DefaultGrainCallConfig(c)
+ for _, o := range opts {
+ o(callConfig)
+ }
+
+ _context := callConfig.Context
+ if _context == nil {
+ _context = c.ActorSystem.Root
+ }
+
+ var lastError error
+
+ for i := 0; i < callConfig.RetryCount; i++ {
+ pid := c.Get(name, kind)
+
+ if pid == nil {
+ return nil, remote.ErrUnknownError
+ }
+
+ timeout := callConfig.Timeout
+ _resp, err := _context.RequestFuture(pid, msg, timeout).Result()
+ if err != nil {
+ plog.Error("cluster.RequestFuture failed", log.Error(err), log.PID("pid", pid))
+ lastError = err
+
+ switch err {
+ case actor.ErrTimeout, remote.ErrTimeout:
+ callConfig.RetryAction(i)
+
+ id := ClusterIdentity{Kind: kind, Identity: name}
+ c.PidCache.Remove(id.Identity, id.Kind)
+
+ continue
+ case actor.ErrDeadLetter, remote.ErrDeadLetter:
+ callConfig.RetryAction(i)
+
+ id := ClusterIdentity{Kind: kind, Identity: name}
+ c.PidCache.Remove(id.Identity, id.Kind)
+
+ continue
+ default:
+ return nil, err
+ }
+ }
+
+ return _resp, nil
+ }
+
+ return nil, lastError
+}
diff --git a/cluster/cluster.pb.go b/cluster/cluster.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..63117b92eb9845bf742335401aef51112bff8554
--- /dev/null
+++ b/cluster/cluster.pb.go
@@ -0,0 +1,1950 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: cluster.proto
+
+package cluster
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type IdentityHandoverAck_State int32
+
+const (
+ IdentityHandoverAck_processed IdentityHandoverAck_State = 0
+ IdentityHandoverAck_incorrect_topology IdentityHandoverAck_State = 1
+)
+
+// Enum value maps for IdentityHandoverAck_State.
+var (
+ IdentityHandoverAck_State_name = map[int32]string{
+ 0: "processed",
+ 1: "incorrect_topology",
+ }
+ IdentityHandoverAck_State_value = map[string]int32{
+ "processed": 0,
+ "incorrect_topology": 1,
+ }
+)
+
+func (x IdentityHandoverAck_State) Enum() *IdentityHandoverAck_State {
+ p := new(IdentityHandoverAck_State)
+ *p = x
+ return p
+}
+
+func (x IdentityHandoverAck_State) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (IdentityHandoverAck_State) Descriptor() protoreflect.EnumDescriptor {
+ return file_cluster_proto_enumTypes[0].Descriptor()
+}
+
+func (IdentityHandoverAck_State) Type() protoreflect.EnumType {
+ return &file_cluster_proto_enumTypes[0]
+}
+
+func (x IdentityHandoverAck_State) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use IdentityHandoverAck_State.Descriptor instead.
+func (IdentityHandoverAck_State) EnumDescriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{4, 0}
+}
+
+// request response call from Identity actor sent to each member
+// asking what activations they hold that belong to the requester
+type IdentityHandoverRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ CurrentTopology *IdentityHandoverRequest_Topology `protobuf:"bytes,1,opt,name=current_topology,json=currentTopology,proto3" json:"current_topology,omitempty"`
+ Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
+ // If the requester passes a delta topology, only return activations which would not be assigned to the member
+ // in the previous topology.
+ DeltaTopology *IdentityHandoverRequest_Topology `protobuf:"bytes,3,opt,name=delta_topology,json=deltaTopology,proto3" json:"delta_topology,omitempty"`
+}
+
+func (x *IdentityHandoverRequest) Reset() {
+ *x = IdentityHandoverRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *IdentityHandoverRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IdentityHandoverRequest) ProtoMessage() {}
+
+func (x *IdentityHandoverRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use IdentityHandoverRequest.ProtoReflect.Descriptor instead.
+func (*IdentityHandoverRequest) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *IdentityHandoverRequest) GetCurrentTopology() *IdentityHandoverRequest_Topology {
+ if x != nil {
+ return x.CurrentTopology
+ }
+ return nil
+}
+
+func (x *IdentityHandoverRequest) GetAddress() string {
+ if x != nil {
+ return x.Address
+ }
+ return ""
+}
+
+func (x *IdentityHandoverRequest) GetDeltaTopology() *IdentityHandoverRequest_Topology {
+ if x != nil {
+ return x.DeltaTopology
+ }
+ return nil
+}
+
+type IdentityHandover struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Actors []*Activation `protobuf:"bytes,1,rep,name=actors,proto3" json:"actors,omitempty"`
+ ChunkId int32 `protobuf:"varint,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"`
+ Final bool `protobuf:"varint,3,opt,name=final,proto3" json:"final,omitempty"`
+ TopologyHash uint64 `protobuf:"varint,4,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ Skipped int32 `protobuf:"varint,5,opt,name=skipped,proto3" json:"skipped,omitempty"` // Total number of activations skipped
+ Sent int32 `protobuf:"varint,6,opt,name=sent,proto3" json:"sent,omitempty"` // Total number of activations sent
+}
+
+func (x *IdentityHandover) Reset() {
+ *x = IdentityHandover{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *IdentityHandover) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IdentityHandover) ProtoMessage() {}
+
+func (x *IdentityHandover) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use IdentityHandover.ProtoReflect.Descriptor instead.
+func (*IdentityHandover) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *IdentityHandover) GetActors() []*Activation {
+ if x != nil {
+ return x.Actors
+ }
+ return nil
+}
+
+func (x *IdentityHandover) GetChunkId() int32 {
+ if x != nil {
+ return x.ChunkId
+ }
+ return 0
+}
+
+func (x *IdentityHandover) GetFinal() bool {
+ if x != nil {
+ return x.Final
+ }
+ return false
+}
+
+func (x *IdentityHandover) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *IdentityHandover) GetSkipped() int32 {
+ if x != nil {
+ return x.Skipped
+ }
+ return 0
+}
+
+func (x *IdentityHandover) GetSent() int32 {
+ if x != nil {
+ return x.Sent
+ }
+ return 0
+}
+
+type RemoteIdentityHandover struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Actors *PackedActivations `protobuf:"bytes,1,opt,name=actors,proto3" json:"actors,omitempty"`
+ ChunkId int32 `protobuf:"varint,2,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"`
+ Final bool `protobuf:"varint,3,opt,name=final,proto3" json:"final,omitempty"`
+ TopologyHash uint64 `protobuf:"varint,4,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ Skipped int32 `protobuf:"varint,5,opt,name=skipped,proto3" json:"skipped,omitempty"`
+ Sent int32 `protobuf:"varint,6,opt,name=sent,proto3" json:"sent,omitempty"`
+}
+
+func (x *RemoteIdentityHandover) Reset() {
+ *x = RemoteIdentityHandover{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteIdentityHandover) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteIdentityHandover) ProtoMessage() {}
+
+func (x *RemoteIdentityHandover) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteIdentityHandover.ProtoReflect.Descriptor instead.
+func (*RemoteIdentityHandover) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *RemoteIdentityHandover) GetActors() *PackedActivations {
+ if x != nil {
+ return x.Actors
+ }
+ return nil
+}
+
+func (x *RemoteIdentityHandover) GetChunkId() int32 {
+ if x != nil {
+ return x.ChunkId
+ }
+ return 0
+}
+
+func (x *RemoteIdentityHandover) GetFinal() bool {
+ if x != nil {
+ return x.Final
+ }
+ return false
+}
+
+func (x *RemoteIdentityHandover) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *RemoteIdentityHandover) GetSkipped() int32 {
+ if x != nil {
+ return x.Skipped
+ }
+ return 0
+}
+
+func (x *RemoteIdentityHandover) GetSent() int32 {
+ if x != nil {
+ return x.Sent
+ }
+ return 0
+}
+
+type PackedActivations struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+ Actors []*PackedActivations_Kind `protobuf:"bytes,2,rep,name=actors,proto3" json:"actors,omitempty"`
+}
+
+func (x *PackedActivations) Reset() {
+ *x = PackedActivations{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PackedActivations) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PackedActivations) ProtoMessage() {}
+
+func (x *PackedActivations) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PackedActivations.ProtoReflect.Descriptor instead.
+func (*PackedActivations) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *PackedActivations) GetAddress() string {
+ if x != nil {
+ return x.Address
+ }
+ return ""
+}
+
+func (x *PackedActivations) GetActors() []*PackedActivations_Kind {
+ if x != nil {
+ return x.Actors
+ }
+ return nil
+}
+
+type IdentityHandoverAck struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ChunkId int32 `protobuf:"varint,1,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"`
+ TopologyHash uint64 `protobuf:"varint,2,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ ProcessingState IdentityHandoverAck_State `protobuf:"varint,3,opt,name=processing_state,json=processingState,proto3,enum=cluster.IdentityHandoverAck_State" json:"processing_state,omitempty"`
+}
+
+func (x *IdentityHandoverAck) Reset() {
+ *x = IdentityHandoverAck{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *IdentityHandoverAck) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IdentityHandoverAck) ProtoMessage() {}
+
+func (x *IdentityHandoverAck) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use IdentityHandoverAck.ProtoReflect.Descriptor instead.
+func (*IdentityHandoverAck) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *IdentityHandoverAck) GetChunkId() int32 {
+ if x != nil {
+ return x.ChunkId
+ }
+ return 0
+}
+
+func (x *IdentityHandoverAck) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *IdentityHandoverAck) GetProcessingState() IdentityHandoverAck_State {
+ if x != nil {
+ return x.ProcessingState
+ }
+ return IdentityHandoverAck_processed
+}
+
+type ClusterIdentity struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Identity string `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
+ Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
+}
+
+func (x *ClusterIdentity) Reset() {
+ *x = ClusterIdentity{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ClusterIdentity) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClusterIdentity) ProtoMessage() {}
+
+func (x *ClusterIdentity) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClusterIdentity.ProtoReflect.Descriptor instead.
+func (*ClusterIdentity) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ClusterIdentity) GetIdentity() string {
+ if x != nil {
+ return x.Identity
+ }
+ return ""
+}
+
+func (x *ClusterIdentity) GetKind() string {
+ if x != nil {
+ return x.Kind
+ }
+ return ""
+}
+
+type Activation struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,2,opt,name=cluster_identity,json=clusterIdentity,proto3" json:"cluster_identity,omitempty"`
+}
+
+func (x *Activation) Reset() {
+ *x = Activation{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Activation) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Activation) ProtoMessage() {}
+
+func (x *Activation) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Activation.ProtoReflect.Descriptor instead.
+func (*Activation) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *Activation) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *Activation) GetClusterIdentity() *ClusterIdentity {
+ if x != nil {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+// Started terminating, not yet removed from IIdentityLookup
+type ActivationTerminating struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,2,opt,name=cluster_identity,json=clusterIdentity,proto3" json:"cluster_identity,omitempty"`
+}
+
+func (x *ActivationTerminating) Reset() {
+ *x = ActivationTerminating{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActivationTerminating) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActivationTerminating) ProtoMessage() {}
+
+func (x *ActivationTerminating) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActivationTerminating.ProtoReflect.Descriptor instead.
+func (*ActivationTerminating) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *ActivationTerminating) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *ActivationTerminating) GetClusterIdentity() *ClusterIdentity {
+ if x != nil {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+// Terminated, removed from lookup
+type ActivationTerminated struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,2,opt,name=cluster_identity,json=clusterIdentity,proto3" json:"cluster_identity,omitempty"`
+}
+
+func (x *ActivationTerminated) Reset() {
+ *x = ActivationTerminated{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActivationTerminated) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActivationTerminated) ProtoMessage() {}
+
+func (x *ActivationTerminated) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActivationTerminated.ProtoReflect.Descriptor instead.
+func (*ActivationTerminated) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *ActivationTerminated) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *ActivationTerminated) GetClusterIdentity() *ClusterIdentity {
+ if x != nil {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+type ActivationRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,1,opt,name=cluster_identity,json=clusterIdentity,proto3" json:"cluster_identity,omitempty"`
+ RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ TopologyHash uint64 `protobuf:"varint,3,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+}
+
+func (x *ActivationRequest) Reset() {
+ *x = ActivationRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActivationRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActivationRequest) ProtoMessage() {}
+
+func (x *ActivationRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[9]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActivationRequest.ProtoReflect.Descriptor instead.
+func (*ActivationRequest) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *ActivationRequest) GetClusterIdentity() *ClusterIdentity {
+ if x != nil {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+func (x *ActivationRequest) GetRequestId() string {
+ if x != nil {
+ return x.RequestId
+ }
+ return ""
+}
+
+func (x *ActivationRequest) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+type ProxyActivationRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,1,opt,name=cluster_identity,json=clusterIdentity,proto3" json:"cluster_identity,omitempty"`
+ ReplacedActivation *actor.PID `protobuf:"bytes,2,opt,name=replaced_activation,json=replacedActivation,proto3" json:"replaced_activation,omitempty"`
+}
+
+func (x *ProxyActivationRequest) Reset() {
+ *x = ProxyActivationRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ProxyActivationRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ProxyActivationRequest) ProtoMessage() {}
+
+func (x *ProxyActivationRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[10]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ProxyActivationRequest.ProtoReflect.Descriptor instead.
+func (*ProxyActivationRequest) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *ProxyActivationRequest) GetClusterIdentity() *ClusterIdentity {
+ if x != nil {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+func (x *ProxyActivationRequest) GetReplacedActivation() *actor.PID {
+ if x != nil {
+ return x.ReplacedActivation
+ }
+ return nil
+}
+
+type ActivationResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+ Failed bool `protobuf:"varint,2,opt,name=failed,proto3" json:"failed,omitempty"`
+ TopologyHash uint64 `protobuf:"varint,3,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+}
+
+func (x *ActivationResponse) Reset() {
+ *x = ActivationResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActivationResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActivationResponse) ProtoMessage() {}
+
+func (x *ActivationResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[11]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActivationResponse.ProtoReflect.Descriptor instead.
+func (*ActivationResponse) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *ActivationResponse) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *ActivationResponse) GetFailed() bool {
+ if x != nil {
+ return x.Failed
+ }
+ return false
+}
+
+func (x *ActivationResponse) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+type ReadyForRebalance struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TopologyHash uint64 `protobuf:"varint,1,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+}
+
+func (x *ReadyForRebalance) Reset() {
+ *x = ReadyForRebalance{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ReadyForRebalance) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReadyForRebalance) ProtoMessage() {}
+
+func (x *ReadyForRebalance) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[12]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReadyForRebalance.ProtoReflect.Descriptor instead.
+func (*ReadyForRebalance) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *ReadyForRebalance) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+type RebalanceCompleted struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TopologyHash uint64 `protobuf:"varint,1,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+}
+
+func (x *RebalanceCompleted) Reset() {
+ *x = RebalanceCompleted{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RebalanceCompleted) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RebalanceCompleted) ProtoMessage() {}
+
+func (x *RebalanceCompleted) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[13]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RebalanceCompleted.ProtoReflect.Descriptor instead.
+func (*RebalanceCompleted) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *RebalanceCompleted) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+type Member struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
+ Port int32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+ Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
+ Kinds []string `protobuf:"bytes,4,rep,name=kinds,proto3" json:"kinds,omitempty"`
+}
+
+func (x *Member) Reset() {
+ *x = Member{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Member) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Member) ProtoMessage() {}
+
+func (x *Member) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[14]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Member.ProtoReflect.Descriptor instead.
+func (*Member) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *Member) GetHost() string {
+ if x != nil {
+ return x.Host
+ }
+ return ""
+}
+
+func (x *Member) GetPort() int32 {
+ if x != nil {
+ return x.Port
+ }
+ return 0
+}
+
+func (x *Member) GetId() string {
+ if x != nil {
+ return x.Id
+ }
+ return ""
+}
+
+func (x *Member) GetKinds() []string {
+ if x != nil {
+ return x.Kinds
+ }
+ return nil
+}
+
+type ClusterTopology struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TopologyHash uint64 `protobuf:"varint,1,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ Members []*Member `protobuf:"bytes,2,rep,name=members,proto3" json:"members,omitempty"`
+ Joined []*Member `protobuf:"bytes,3,rep,name=joined,proto3" json:"joined,omitempty"`
+ Left []*Member `protobuf:"bytes,4,rep,name=left,proto3" json:"left,omitempty"`
+ Blocked []string `protobuf:"bytes,5,rep,name=blocked,proto3" json:"blocked,omitempty"`
+}
+
+func (x *ClusterTopology) Reset() {
+ *x = ClusterTopology{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ClusterTopology) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClusterTopology) ProtoMessage() {}
+
+func (x *ClusterTopology) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[15]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClusterTopology.ProtoReflect.Descriptor instead.
+func (*ClusterTopology) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *ClusterTopology) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *ClusterTopology) GetMembers() []*Member {
+ if x != nil {
+ return x.Members
+ }
+ return nil
+}
+
+func (x *ClusterTopology) GetJoined() []*Member {
+ if x != nil {
+ return x.Joined
+ }
+ return nil
+}
+
+func (x *ClusterTopology) GetLeft() []*Member {
+ if x != nil {
+ return x.Left
+ }
+ return nil
+}
+
+func (x *ClusterTopology) GetBlocked() []string {
+ if x != nil {
+ return x.Blocked
+ }
+ return nil
+}
+
+type ClusterTopologyNotification struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MemberId string `protobuf:"bytes,1,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"`
+ TopologyHash uint32 `protobuf:"varint,2,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ LeaderId string `protobuf:"bytes,3,opt,name=leader_id,json=leaderId,proto3" json:"leader_id,omitempty"`
+}
+
+func (x *ClusterTopologyNotification) Reset() {
+ *x = ClusterTopologyNotification{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[16]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ClusterTopologyNotification) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClusterTopologyNotification) ProtoMessage() {}
+
+func (x *ClusterTopologyNotification) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[16]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClusterTopologyNotification.ProtoReflect.Descriptor instead.
+func (*ClusterTopologyNotification) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{16}
+}
+
+func (x *ClusterTopologyNotification) GetMemberId() string {
+ if x != nil {
+ return x.MemberId
+ }
+ return ""
+}
+
+func (x *ClusterTopologyNotification) GetTopologyHash() uint32 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *ClusterTopologyNotification) GetLeaderId() string {
+ if x != nil {
+ return x.LeaderId
+ }
+ return ""
+}
+
+type MemberHeartbeat struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ActorStatistics *ActorStatistics `protobuf:"bytes,1,opt,name=actor_statistics,json=actorStatistics,proto3" json:"actor_statistics,omitempty"`
+}
+
+func (x *MemberHeartbeat) Reset() {
+ *x = MemberHeartbeat{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[17]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MemberHeartbeat) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MemberHeartbeat) ProtoMessage() {}
+
+func (x *MemberHeartbeat) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[17]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MemberHeartbeat.ProtoReflect.Descriptor instead.
+func (*MemberHeartbeat) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *MemberHeartbeat) GetActorStatistics() *ActorStatistics {
+ if x != nil {
+ return x.ActorStatistics
+ }
+ return nil
+}
+
+type ActorStatistics struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ ActorCount map[string]int64 `protobuf:"bytes,1,rep,name=actor_count,json=actorCount,proto3" json:"actor_count,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
+}
+
+func (x *ActorStatistics) Reset() {
+ *x = ActorStatistics{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[18]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActorStatistics) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActorStatistics) ProtoMessage() {}
+
+func (x *ActorStatistics) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[18]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActorStatistics.ProtoReflect.Descriptor instead.
+func (*ActorStatistics) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{18}
+}
+
+func (x *ActorStatistics) GetActorCount() map[string]int64 {
+ if x != nil {
+ return x.ActorCount
+ }
+ return nil
+}
+
+type IdentityHandoverRequest_Topology struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TopologyHash uint64 `protobuf:"varint,1,opt,name=topology_hash,json=topologyHash,proto3" json:"topology_hash,omitempty"`
+ Members []*Member `protobuf:"bytes,3,rep,name=members,proto3" json:"members,omitempty"`
+}
+
+func (x *IdentityHandoverRequest_Topology) Reset() {
+ *x = IdentityHandoverRequest_Topology{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[19]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *IdentityHandoverRequest_Topology) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IdentityHandoverRequest_Topology) ProtoMessage() {}
+
+func (x *IdentityHandoverRequest_Topology) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[19]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use IdentityHandoverRequest_Topology.ProtoReflect.Descriptor instead.
+func (*IdentityHandoverRequest_Topology) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{0, 0}
+}
+
+func (x *IdentityHandoverRequest_Topology) GetTopologyHash() uint64 {
+ if x != nil {
+ return x.TopologyHash
+ }
+ return 0
+}
+
+func (x *IdentityHandoverRequest_Topology) GetMembers() []*Member {
+ if x != nil {
+ return x.Members
+ }
+ return nil
+}
+
+type PackedActivations_Kind struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Activations []*PackedActivations_Activation `protobuf:"bytes,2,rep,name=activations,proto3" json:"activations,omitempty"`
+}
+
+func (x *PackedActivations_Kind) Reset() {
+ *x = PackedActivations_Kind{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[20]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PackedActivations_Kind) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PackedActivations_Kind) ProtoMessage() {}
+
+func (x *PackedActivations_Kind) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[20]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PackedActivations_Kind.ProtoReflect.Descriptor instead.
+func (*PackedActivations_Kind) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{3, 0}
+}
+
+func (x *PackedActivations_Kind) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *PackedActivations_Kind) GetActivations() []*PackedActivations_Activation {
+ if x != nil {
+ return x.Activations
+ }
+ return nil
+}
+
+type PackedActivations_Activation struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Identity string `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"`
+ ActivationId string `protobuf:"bytes,2,opt,name=activation_id,json=activationId,proto3" json:"activation_id,omitempty"`
+}
+
+func (x *PackedActivations_Activation) Reset() {
+ *x = PackedActivations_Activation{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_cluster_proto_msgTypes[21]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PackedActivations_Activation) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PackedActivations_Activation) ProtoMessage() {}
+
+func (x *PackedActivations_Activation) ProtoReflect() protoreflect.Message {
+ mi := &file_cluster_proto_msgTypes[21]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PackedActivations_Activation.ProtoReflect.Descriptor instead.
+func (*PackedActivations_Activation) Descriptor() ([]byte, []int) {
+ return file_cluster_proto_rawDescGZIP(), []int{3, 1}
+}
+
+func (x *PackedActivations_Activation) GetIdentity() string {
+ if x != nil {
+ return x.Identity
+ }
+ return ""
+}
+
+func (x *PackedActivations_Activation) GetActivationId() string {
+ if x != nil {
+ return x.ActivationId
+ }
+ return ""
+}
+
+var File_cluster_proto protoreflect.FileDescriptor
+
+var file_cluster_proto_rawDesc = []byte{
+ 0x0a, 0x0d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
+ 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb7, 0x02, 0x0a, 0x17, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
+ 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x54, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x70,
+ 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x61,
+ 0x6e, 0x64, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x6f,
+ 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x52, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54,
+ 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
+ 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
+ 0x73, 0x12, 0x50, 0x0a, 0x0e, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x74, 0x6f, 0x70, 0x6f, 0x6c,
+ 0x6f, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6c, 0x75, 0x73,
+ 0x74, 0x65, 0x72, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64,
+ 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x6f, 0x70, 0x6f,
+ 0x6c, 0x6f, 0x67, 0x79, 0x52, 0x0d, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x54, 0x6f, 0x70, 0x6f, 0x6c,
+ 0x6f, 0x67, 0x79, 0x1a, 0x5a, 0x0a, 0x08, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x12,
+ 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79,
+ 0x48, 0x61, 0x73, 0x68, 0x12, 0x29, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18,
+ 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e,
+ 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x22,
+ 0xc3, 0x01, 0x0a, 0x10, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64,
+ 0x6f, 0x76, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01,
+ 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x41,
+ 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72,
+ 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05,
+ 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x69, 0x6e,
+ 0x61, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68,
+ 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c,
+ 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6b, 0x69, 0x70, 0x70,
+ 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x73, 0x6b, 0x69, 0x70, 0x70, 0x65,
+ 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52,
+ 0x04, 0x73, 0x65, 0x6e, 0x74, 0x22, 0xd0, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+ 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64, 0x6f, 0x76, 0x65, 0x72,
+ 0x12, 0x32, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x1a, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65,
+ 0x64, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x06, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x64, 0x12,
+ 0x14, 0x0a, 0x05, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05,
+ 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67,
+ 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f,
+ 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6b,
+ 0x69, 0x70, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x73, 0x6b, 0x69,
+ 0x70, 0x70, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01,
+ 0x28, 0x05, 0x52, 0x04, 0x73, 0x65, 0x6e, 0x74, 0x22, 0x9a, 0x02, 0x0a, 0x11, 0x50, 0x61, 0x63,
+ 0x6b, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18,
+ 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x6f,
+ 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74,
+ 0x65, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x06, 0x61, 0x63, 0x74, 0x6f, 0x72,
+ 0x73, 0x1a, 0x63, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a,
+ 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03,
+ 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x63,
+ 0x6b, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x41,
+ 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+ 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
+ 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xd4, 0x01, 0x0a, 0x13, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
+ 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64, 0x6f, 0x76, 0x65, 0x72, 0x41, 0x63, 0x6b, 0x12, 0x19, 0x0a,
+ 0x08, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
+ 0x07, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f,
+ 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
+ 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4d, 0x0a,
+ 0x10, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74,
+ 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
+ 0x72, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x61, 0x6e, 0x64, 0x6f, 0x76,
+ 0x65, 0x72, 0x41, 0x63, 0x6b, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f, 0x70, 0x72, 0x6f,
+ 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x2e, 0x0a, 0x05,
+ 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+ 0x65, 0x64, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63,
+ 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x10, 0x01, 0x22, 0x41, 0x0a, 0x0f,
+ 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12,
+ 0x1a, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6b,
+ 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22,
+ 0x6f, 0x0a, 0x0a, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a,
+ 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74,
+ 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x10, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e,
+ 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52,
+ 0x0f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+ 0x22, 0x7a, 0x0a, 0x15, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x65,
+ 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x03, 0x70, 0x69, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x10, 0x63, 0x6c, 0x75, 0x73, 0x74,
+ 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x75, 0x73,
+ 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x63, 0x6c, 0x75,
+ 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x79, 0x0a, 0x14,
+ 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e,
+ 0x61, 0x74, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x03, 0x70,
+ 0x69, 0x64, 0x12, 0x43, 0x0a, 0x10, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64,
+ 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64,
+ 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49,
+ 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x9c, 0x01, 0x0a, 0x11, 0x41, 0x63, 0x74, 0x69,
+ 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a,
+ 0x10, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
+ 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
+ 0x72, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
+ 0x79, 0x52, 0x0f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
+ 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
+ 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61,
+ 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f,
+ 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x22, 0x9a, 0x01, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x78, 0x79,
+ 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x43, 0x0a, 0x10, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65,
+ 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65,
+ 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64,
+ 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x13, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63,
+ 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52,
+ 0x12, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x12, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x03, 0x70, 0x69, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65,
+ 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12,
+ 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68,
+ 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79,
+ 0x48, 0x61, 0x73, 0x68, 0x22, 0x38, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x79, 0x46, 0x6f, 0x72,
+ 0x52, 0x65, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70,
+ 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
+ 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x22, 0x39,
+ 0x0a, 0x12, 0x52, 0x65, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c,
+ 0x65, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79,
+ 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f, 0x70,
+ 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x22, 0x56, 0x0a, 0x06, 0x4d, 0x65, 0x6d,
+ 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18,
+ 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
+ 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6b,
+ 0x69, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x69, 0x6e, 0x64,
+ 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x0f, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x54, 0x6f, 0x70,
+ 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67,
+ 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x74, 0x6f,
+ 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x29, 0x0a, 0x07, 0x6d, 0x65,
+ 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x07, 0x6d, 0x65,
+ 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x27, 0x0a, 0x06, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x18,
+ 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e,
+ 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x06, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x23,
+ 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x04, 0x6c,
+ 0x65, 0x66, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x05,
+ 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x7c, 0x0a,
+ 0x1b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79,
+ 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09,
+ 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x6f, 0x70,
+ 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
+ 0x52, 0x0c, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1b,
+ 0x0a, 0x09, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x08, 0x6c, 0x65, 0x61, 0x64, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x0f, 0x4d,
+ 0x65, 0x6d, 0x62, 0x65, 0x72, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x43,
+ 0x0a, 0x10, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69,
+ 0x63, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74,
+ 0x65, 0x72, 0x2e, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69,
+ 0x63, 0x73, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74,
+ 0x69, 0x63, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61,
+ 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x49, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72,
+ 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74,
+ 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e,
+ 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x75,
+ 0x6e, 0x74, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74,
+ 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
+ 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61,
+ 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62,
+ 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_cluster_proto_rawDescOnce sync.Once
+ file_cluster_proto_rawDescData = file_cluster_proto_rawDesc
+)
+
+func file_cluster_proto_rawDescGZIP() []byte {
+ file_cluster_proto_rawDescOnce.Do(func() {
+ file_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_cluster_proto_rawDescData)
+ })
+ return file_cluster_proto_rawDescData
+}
+
+var file_cluster_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
+var file_cluster_proto_goTypes = []interface{}{
+ (IdentityHandoverAck_State)(0), // 0: cluster.IdentityHandoverAck.State
+ (*IdentityHandoverRequest)(nil), // 1: cluster.IdentityHandoverRequest
+ (*IdentityHandover)(nil), // 2: cluster.IdentityHandover
+ (*RemoteIdentityHandover)(nil), // 3: cluster.RemoteIdentityHandover
+ (*PackedActivations)(nil), // 4: cluster.PackedActivations
+ (*IdentityHandoverAck)(nil), // 5: cluster.IdentityHandoverAck
+ (*ClusterIdentity)(nil), // 6: cluster.ClusterIdentity
+ (*Activation)(nil), // 7: cluster.Activation
+ (*ActivationTerminating)(nil), // 8: cluster.ActivationTerminating
+ (*ActivationTerminated)(nil), // 9: cluster.ActivationTerminated
+ (*ActivationRequest)(nil), // 10: cluster.ActivationRequest
+ (*ProxyActivationRequest)(nil), // 11: cluster.ProxyActivationRequest
+ (*ActivationResponse)(nil), // 12: cluster.ActivationResponse
+ (*ReadyForRebalance)(nil), // 13: cluster.ReadyForRebalance
+ (*RebalanceCompleted)(nil), // 14: cluster.RebalanceCompleted
+ (*Member)(nil), // 15: cluster.Member
+ (*ClusterTopology)(nil), // 16: cluster.ClusterTopology
+ (*ClusterTopologyNotification)(nil), // 17: cluster.ClusterTopologyNotification
+ (*MemberHeartbeat)(nil), // 18: cluster.MemberHeartbeat
+ (*ActorStatistics)(nil), // 19: cluster.ActorStatistics
+ (*IdentityHandoverRequest_Topology)(nil), // 20: cluster.IdentityHandoverRequest.Topology
+ (*PackedActivations_Kind)(nil), // 21: cluster.PackedActivations.Kind
+ (*PackedActivations_Activation)(nil), // 22: cluster.PackedActivations.Activation
+ nil, // 23: cluster.ActorStatistics.ActorCountEntry
+ (*actor.PID)(nil), // 24: actor.PID
+}
+var file_cluster_proto_depIdxs = []int32{
+ 20, // 0: cluster.IdentityHandoverRequest.current_topology:type_name -> cluster.IdentityHandoverRequest.Topology
+ 20, // 1: cluster.IdentityHandoverRequest.delta_topology:type_name -> cluster.IdentityHandoverRequest.Topology
+ 7, // 2: cluster.IdentityHandover.actors:type_name -> cluster.Activation
+ 4, // 3: cluster.RemoteIdentityHandover.actors:type_name -> cluster.PackedActivations
+ 21, // 4: cluster.PackedActivations.actors:type_name -> cluster.PackedActivations.Kind
+ 0, // 5: cluster.IdentityHandoverAck.processing_state:type_name -> cluster.IdentityHandoverAck.State
+ 24, // 6: cluster.Activation.pid:type_name -> actor.PID
+ 6, // 7: cluster.Activation.cluster_identity:type_name -> cluster.ClusterIdentity
+ 24, // 8: cluster.ActivationTerminating.pid:type_name -> actor.PID
+ 6, // 9: cluster.ActivationTerminating.cluster_identity:type_name -> cluster.ClusterIdentity
+ 24, // 10: cluster.ActivationTerminated.pid:type_name -> actor.PID
+ 6, // 11: cluster.ActivationTerminated.cluster_identity:type_name -> cluster.ClusterIdentity
+ 6, // 12: cluster.ActivationRequest.cluster_identity:type_name -> cluster.ClusterIdentity
+ 6, // 13: cluster.ProxyActivationRequest.cluster_identity:type_name -> cluster.ClusterIdentity
+ 24, // 14: cluster.ProxyActivationRequest.replaced_activation:type_name -> actor.PID
+ 24, // 15: cluster.ActivationResponse.pid:type_name -> actor.PID
+ 15, // 16: cluster.ClusterTopology.members:type_name -> cluster.Member
+ 15, // 17: cluster.ClusterTopology.joined:type_name -> cluster.Member
+ 15, // 18: cluster.ClusterTopology.left:type_name -> cluster.Member
+ 19, // 19: cluster.MemberHeartbeat.actor_statistics:type_name -> cluster.ActorStatistics
+ 23, // 20: cluster.ActorStatistics.actor_count:type_name -> cluster.ActorStatistics.ActorCountEntry
+ 15, // 21: cluster.IdentityHandoverRequest.Topology.members:type_name -> cluster.Member
+ 22, // 22: cluster.PackedActivations.Kind.activations:type_name -> cluster.PackedActivations.Activation
+ 23, // [23:23] is the sub-list for method output_type
+ 23, // [23:23] is the sub-list for method input_type
+ 23, // [23:23] is the sub-list for extension type_name
+ 23, // [23:23] is the sub-list for extension extendee
+ 0, // [0:23] is the sub-list for field type_name
+}
+
+func init() { file_cluster_proto_init() }
+func file_cluster_proto_init() {
+ if File_cluster_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_cluster_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*IdentityHandoverRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*IdentityHandover); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteIdentityHandover); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PackedActivations); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*IdentityHandoverAck); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ClusterIdentity); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Activation); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActivationTerminating); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActivationTerminated); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActivationRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ProxyActivationRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActivationResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ReadyForRebalance); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RebalanceCompleted); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Member); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ClusterTopology); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ClusterTopologyNotification); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MemberHeartbeat); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActorStatistics); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*IdentityHandoverRequest_Topology); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PackedActivations_Kind); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_cluster_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PackedActivations_Activation); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_cluster_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 23,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_cluster_proto_goTypes,
+ DependencyIndexes: file_cluster_proto_depIdxs,
+ EnumInfos: file_cluster_proto_enumTypes,
+ MessageInfos: file_cluster_proto_msgTypes,
+ }.Build()
+ File_cluster_proto = out.File
+ file_cluster_proto_rawDesc = nil
+ file_cluster_proto_goTypes = nil
+ file_cluster_proto_depIdxs = nil
+}
diff --git a/cluster/cluster.proto b/cluster/cluster.proto
new file mode 100644
index 0000000000000000000000000000000000000000..2a76da9c2e19313bfe12416ff001e57ef870d6de
--- /dev/null
+++ b/cluster/cluster.proto
@@ -0,0 +1,142 @@
+syntax = "proto3";
+package cluster;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster";
+import "actor.proto";
+
+//request response call from Identity actor sent to each member
+//asking what activations they hold that belong to the requester
+message IdentityHandoverRequest {
+ Topology current_topology = 1;
+ string address = 2;
+ // If the requester passes a delta topology, only return activations which would not be assigned to the member
+ // in the previous topology.
+ Topology delta_topology = 3;
+ message Topology{
+ uint64 topology_hash = 1;
+ repeated Member members = 3;
+ }
+}
+
+message IdentityHandover {
+ repeated Activation actors = 1;
+ int32 chunk_id = 2;
+ bool final = 3;
+ uint64 topology_hash = 4;
+ int32 skipped = 5; // Total number of activations skipped
+ int32 sent = 6; // Total number of activations sent
+}
+
+message RemoteIdentityHandover {
+ PackedActivations actors = 1;
+ int32 chunk_id = 2;
+ bool final = 3;
+ uint64 topology_hash = 4;
+ int32 skipped = 5;
+ int32 sent = 6;
+}
+
+message PackedActivations{
+ string address = 1;
+ repeated Kind actors = 2;
+ message Kind{
+ string name = 1;
+ repeated Activation activations = 2;
+ }
+ message Activation{
+ string identity = 1;
+ string activation_id = 2;
+ }
+}
+
+message IdentityHandoverAck {
+ int32 chunk_id = 1;
+ uint64 topology_hash = 2;
+ State processing_state = 3;
+ enum State {
+ processed = 0;
+ incorrect_topology = 1;
+ }
+}
+
+message ClusterIdentity{
+ string identity = 1;
+ string kind = 2;
+}
+
+message Activation {
+ actor.PID pid = 1;
+ ClusterIdentity cluster_identity = 2;
+}
+
+// Started terminating, not yet removed from IIdentityLookup
+message ActivationTerminating {
+ actor.PID pid = 1;
+ ClusterIdentity cluster_identity = 2;
+}
+
+// Terminated, removed from lookup
+message ActivationTerminated {
+ actor.PID pid = 1;
+ ClusterIdentity cluster_identity = 2;
+}
+
+message ActivationRequest {
+ ClusterIdentity cluster_identity = 1;
+ string request_id = 2;
+ uint64 topology_hash = 3;
+}
+
+message ProxyActivationRequest {
+ ClusterIdentity cluster_identity = 1;
+ actor.PID replaced_activation = 2;
+}
+
+message ActivationResponse {
+ actor.PID pid = 1;
+ bool failed = 2;
+ uint64 topology_hash = 3;
+}
+
+message ReadyForRebalance {
+ uint64 topology_hash = 1;
+}
+message RebalanceCompleted {
+ uint64 topology_hash = 1;
+}
+
+message Member {
+ string host = 1;
+ int32 port = 2;
+ string id = 3;
+ repeated string kinds = 4;
+}
+
+message ClusterTopology {
+ uint64 topology_hash = 1;
+ repeated Member members = 2;
+ repeated Member joined = 3;
+ repeated Member left = 4;
+ repeated string blocked = 5;
+}
+
+message ClusterTopologyNotification {
+ string member_id = 1;
+ uint32 topology_hash = 2;
+ string leader_id = 3;
+}
+
+message MemberHeartbeat {
+ ActorStatistics actor_statistics = 1;
+}
+
+message ActorStatistics {
+ map actor_count = 1;
+}
+
+
+
+
+//keys to implement initially
+//topology - value is repeated members, this can replace ClusterTopologyNotification, as it would be the same, but better
+//heartbeat - value is unit, or sender timestamp?
+//leader - value is leader member id
\ No newline at end of file
diff --git a/cluster/cluster_config_context.go b/cluster/cluster_config_context.go
new file mode 100644
index 0000000000000000000000000000000000000000..4075bba58f6e97758988b2ddc4a5d137226287e5
--- /dev/null
+++ b/cluster/cluster_config_context.go
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package cluster
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+const (
+ defaultActorRequestTimeout = 5 * time.Second
+ defaultRequestsLogThrottlePeriod = 2 * time.Second
+ defaultMaxNumberOfEvetsInRequestLogThrottledPeriod int = 3
+)
+
+// ClusterContextConfig is used to configure cluster context parameters
+type ClusterContextConfig struct {
+ ActorRequestTimeout time.Duration
+ RequestsLogThrottlePeriod time.Duration
+ MaxNumberOfEventsInRequestLogThrottledPeriod int
+ RetryAction func(int) int
+ requestLogThrottle actor.ShouldThrottle
+}
+
+// NewDefaultClusterContextConfig creates a mew ClusterContextConfig with default
+// values and returns a pointer to its memory address
+func NewDefaultClusterContextConfig() *ClusterContextConfig {
+ config := ClusterContextConfig{
+ ActorRequestTimeout: defaultActorRequestTimeout,
+ RequestsLogThrottlePeriod: defaultRequestsLogThrottlePeriod,
+ MaxNumberOfEventsInRequestLogThrottledPeriod: defaultMaxNumberOfEvetsInRequestLogThrottledPeriod,
+ RetryAction: defaultRetryAction,
+ requestLogThrottle: actor.NewThrottle(
+ int32(defaultMaxNumberOfEvetsInRequestLogThrottledPeriod),
+ defaultRequestsLogThrottlePeriod,
+ func(i int32) {
+ plog.Info(fmt.Sprintf("Throttled %d Request logs", i))
+ },
+ ),
+ }
+ return &config
+}
diff --git a/cluster/cluster_identity.go b/cluster/cluster_identity.go
new file mode 100644
index 0000000000000000000000000000000000000000..efff49571990e534d0da233d959f9b34cd2b5a14
--- /dev/null
+++ b/cluster/cluster_identity.go
@@ -0,0 +1,36 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/ctxext"
+)
+
+func (ci *ClusterIdentity) AsKey() string {
+ return ci.Kind + "/" + ci.Identity
+}
+
+var ciExtensionId = ctxext.NextContextExtensionID()
+
+// remove
+func (ci *ClusterIdentity) ToShortString() string {
+ return ci.Kind + "/" + ci.Identity
+}
+
+func NewClusterIdentity(identity string, kind string) *ClusterIdentity {
+ return &ClusterIdentity{
+ Identity: identity,
+ Kind: kind,
+ }
+}
+
+func (ci *ClusterIdentity) ExtensionID() ctxext.ContextExtensionID {
+ return ciExtensionId
+}
+
+func GetClusterIdentity(ctx actor.ExtensionContext) *ClusterIdentity {
+ return ctx.Get(ciExtensionId).(*ClusterIdentity)
+}
+
+func SetClusterIdentity(ctx actor.ExtensionContext, ci *ClusterIdentity) {
+ ctx.Set(ci)
+}
diff --git a/cluster/cluster_provider.go b/cluster/cluster_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..f717f73a84a48ae0522e8506c7b636ce47ac15a9
--- /dev/null
+++ b/cluster/cluster_provider.go
@@ -0,0 +1,12 @@
+package cluster
+
+//type ClusterState struct {
+// BannedMembers []string `json:"blockedMembers"`
+//}
+
+type ClusterProvider interface {
+ StartMember(cluster *Cluster) error
+ StartClient(cluster *Cluster) error
+ Shutdown(graceful bool) error
+ // UpdateClusterState(state ClusterState) error
+}
diff --git a/cluster/cluster_test.go b/cluster/cluster_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e2fb3996a5ba03fa3c19e1cb2da3eee580bc119e
--- /dev/null
+++ b/cluster/cluster_test.go
@@ -0,0 +1,192 @@
+package cluster
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/assert"
+)
+
+// inmemoryProvider use for test
+type inmemoryProvider struct {
+ cluster *Cluster
+ members map[string]*Member
+ self *Member
+}
+
+func newInmemoryProvider() *inmemoryProvider {
+ return &inmemoryProvider{members: map[string]*Member{}}
+}
+
+func (p *inmemoryProvider) init(c *Cluster) error {
+ name := c.Config.Name
+ host, port, err := c.ActorSystem.GetHostPort()
+ if err != nil {
+ return err
+ }
+ p.cluster = c
+ p.self = &Member{
+ Host: host,
+ Port: int32(port),
+ Id: fmt.Sprintf("%s@%s:%d", name, host, port),
+ Kinds: c.GetClusterKinds(),
+ }
+
+ return nil
+}
+
+func (p *inmemoryProvider) publishClusterTopologyEvent() {
+ var members Members
+ for _, m := range p.members {
+ members = append(members, m)
+ }
+
+ res := members
+
+ p.cluster.MemberList.UpdateClusterTopology(res)
+ // p.cluster.ActorSystem.EventStream.Publish(res)
+}
+
+func (p *inmemoryProvider) StartMember(c *Cluster) error {
+ err := p.init(c)
+ if err != nil {
+ return err
+ }
+ p.members[p.self.Id] = p.self
+ p.publishClusterTopologyEvent()
+ return nil
+}
+
+func (p *inmemoryProvider) StartClient(c *Cluster) error {
+ err := p.init(c)
+ if err != nil {
+ return err
+ }
+ p.publishClusterTopologyEvent()
+ return nil
+}
+
+func (p *inmemoryProvider) Shutdown(graceful bool) error {
+ delete(p.members, p.self.Id)
+
+ return nil
+}
+
+type fakeIdentityLookup struct {
+ m sync.Map
+}
+
+func (l *fakeIdentityLookup) Get(identity *ClusterIdentity) *actor.PID {
+ if val, ok := l.m.Load(identity.Identity); ok {
+ return val.(*actor.PID)
+ } else {
+ // pid := actor.NewPID("127.0.0.1", fmt.Sprintf("%s/%s", identity.Kind, identity.Identity))
+ // l.m.Store(identity.Identity, pid)
+ // return pid
+ }
+ return nil
+}
+
+func (l *fakeIdentityLookup) RemovePid(identity *ClusterIdentity, pid *actor.PID) {
+ if existPid := l.Get(identity); existPid.Equal(pid) {
+ l.m.Delete(identity.Identity)
+ }
+}
+
+func (lu *fakeIdentityLookup) Setup(cluster *Cluster, kinds []string, isClient bool) {
+}
+
+func (lu *fakeIdentityLookup) Shutdown() {
+}
+
+func newClusterForTest(name string, cp ClusterProvider, opts ...ConfigOption) *Cluster {
+ system := actor.NewActorSystem()
+ lookup := fakeIdentityLookup{}
+ cfg := Configure(name, cp, &lookup, remote.Configure("127.0.0.1", 0), opts...)
+ c := New(system, cfg)
+
+ c.MemberList = NewMemberList(c)
+ c.Config.RequestTimeoutTime = 1 * time.Second
+ c.Remote = remote.NewRemote(system, c.Config.RemoteConfig)
+ return c
+}
+
+func TestCluster_Call(t *testing.T) {
+ t.Skipf("Maintaining")
+ assert := assert.New(t)
+
+ members := Members{
+ {
+ Id: "1",
+ Host: "nonhost",
+ Port: -1,
+ Kinds: []string{"kind"},
+ },
+ }
+ c := newClusterForTest("mycluster", nil)
+ c.MemberList.UpdateClusterTopology(members)
+ t.Run("invalid kind", func(t *testing.T) {
+ msg := struct{}{}
+ resp, err := c.Request("name", "nonkind", &msg)
+ assert.Equal(remote.ErrUnAvailable, err)
+ assert.Nil(resp)
+ })
+
+ // FIXME: testcase
+ // t.Run("timeout", func(t *testing.T) {
+ // msg := struct{}{}
+ // callopts := NewGrainCallOptions(c).WithRetry(2).WithRequestTimeout(1 * time.Second)
+ // resp, err := c.Call("name", "kind", &msg, callopts)
+ // assert.Equalf(Remote.ErrUnknownError, err, "%v", err)
+ // assert.Nil(resp)
+ // })
+
+ testProps := actor.PropsFromFunc(
+ func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *struct{ Code int }:
+ msg.Code++
+ context.Respond(msg)
+ }
+ })
+ pid := c.ActorSystem.Root.Spawn(testProps)
+ assert.NotNil(pid)
+ c.PidCache.Set("name", "kind", pid)
+ t.Run("normal", func(t *testing.T) {
+ msg := struct{ Code int }{9527}
+ resp, err := c.Request("name", "kind", &msg)
+ assert.NoError(err)
+ assert.Equal(&struct{ Code int }{9528}, resp)
+ })
+ // t.Fatalf("need more testcases for cluster.Call")
+}
+
+func TestCluster_Get(t *testing.T) {
+ t.Skipf("Maintaining")
+ cp := newInmemoryProvider()
+ kind := NewKind("kind", actor.PropsFromFunc(func(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ _ = msg
+ }
+ }))
+ c := newClusterForTest("mycluster", cp, WithKinds(kind))
+ c.StartMember()
+ cp.publishClusterTopologyEvent()
+ t.Run("invalid kind", func(t *testing.T) {
+ assert := assert.New(t)
+ assert.Equal(1, c.MemberList.Length())
+ pid := c.Get("name", "nonkind")
+ assert.Nil(pid)
+ })
+
+ t.Run("ok", func(t *testing.T) {
+ assert := assert.New(t)
+ pid := c.Get("name", "kind")
+ assert.NotNil(pid)
+ })
+}
diff --git a/cluster/cluster_test_tool/build.sh b/cluster/cluster_test_tool/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..144b2ffeddacabce2d67dfefd68d53e91c41c21f
--- /dev/null
+++ b/cluster/cluster_test_tool/build.sh
@@ -0,0 +1,2 @@
+protoc -I=../../actor --go_out=. --go_opt=paths=source_relative --proto_path=. pubsub_cluster.proto
+
diff --git a/cluster/cluster_test_tool/cluster_fixture.go b/cluster/cluster_test_tool/cluster_fixture.go
new file mode 100644
index 0000000000000000000000000000000000000000..e390aca37ae65bfcc85968cc146e92bc5dd3d5d7
--- /dev/null
+++ b/cluster/cluster_test_tool/cluster_fixture.go
@@ -0,0 +1,224 @@
+package cluster_test_tool
+
+import (
+ "context"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/test"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/google/uuid"
+ "golang.org/x/sync/errgroup"
+)
+
+type ClusterFixture interface {
+ GetMembers() []*cluster.Cluster
+ GetClusterSize() int
+ SpawnNode() *cluster.Cluster
+ RemoveNode(node *cluster.Cluster, graceful bool)
+ ShutDown()
+}
+
+type ClusterFixtureConfig struct {
+ GetClusterKinds func() []*cluster.Kind
+ GetClusterProvider func() cluster.ClusterProvider
+ Configure func(*cluster.Config) *cluster.Config
+ GetIdentityLookup func(clusterName string) cluster.IdentityLookup
+ OnDeposing func()
+}
+
+type ClusterFixtureOption func(*ClusterFixtureConfig)
+
+// WithGetClusterKinds sets the cluster kinds for the cluster fixture
+func WithGetClusterKinds(getKinds func() []*cluster.Kind) ClusterFixtureOption {
+ return func(c *ClusterFixtureConfig) {
+ c.GetClusterKinds = getKinds
+ }
+}
+
+// WithClusterConfigure sets the cluster configure function for the cluster fixture
+func WithClusterConfigure(configure func(*cluster.Config) *cluster.Config) ClusterFixtureOption {
+ return func(c *ClusterFixtureConfig) {
+ c.Configure = configure
+ }
+}
+
+// WithGetClusterProvider sets the cluster provider for the cluster fixture
+func WithGetClusterProvider(getProvider func() cluster.ClusterProvider) ClusterFixtureOption {
+ return func(c *ClusterFixtureConfig) {
+ c.GetClusterProvider = getProvider
+ }
+}
+
+// WithGetIdentityLookup sets the identity lookup function for the cluster fixture
+func WithGetIdentityLookup(identityLookup func(clusterName string) cluster.IdentityLookup) ClusterFixtureOption {
+ return func(c *ClusterFixtureConfig) {
+ c.GetIdentityLookup = identityLookup
+ }
+}
+
+// WithOnDeposing sets the on deposing function for the cluster fixture
+func WithOnDeposing(onDeposing func()) ClusterFixtureOption {
+ return func(c *ClusterFixtureConfig) {
+ c.OnDeposing = onDeposing
+ }
+}
+
+const InvalidIdentity string = "invalid"
+
+type BaseClusterFixture struct {
+ clusterName string
+ clusterSize int
+ config *ClusterFixtureConfig
+ members []*cluster.Cluster
+}
+
+func NewBaseClusterFixture(clusterSize int, opts ...ClusterFixtureOption) *BaseClusterFixture {
+ config := &ClusterFixtureConfig{
+ GetClusterKinds: func() []*cluster.Kind { return make([]*cluster.Kind, 0) },
+ GetClusterProvider: func() cluster.ClusterProvider { return test.NewTestProvider(test.NewInMemAgent()) },
+ Configure: func(c *cluster.Config) *cluster.Config { return c },
+ GetIdentityLookup: func(clusterName string) cluster.IdentityLookup { return disthash.New() },
+ OnDeposing: func() {},
+ }
+ for _, opt := range opts {
+ opt(config)
+ }
+
+ fixTure := &BaseClusterFixture{
+ clusterSize: clusterSize,
+ clusterName: "test-cluster-" + uuid.NewString()[0:6],
+ config: config,
+ members: make([]*cluster.Cluster, 0),
+ }
+ return fixTure
+}
+
+// Initialize initializes the cluster fixture
+func (b *BaseClusterFixture) Initialize() {
+ nodes := b.spawnClusterNodes()
+ b.members = append(b.members, nodes...)
+}
+
+func (b *BaseClusterFixture) GetMembers() []*cluster.Cluster {
+ return b.members
+}
+
+func (b *BaseClusterFixture) GetClusterSize() int {
+ return b.clusterSize
+}
+
+func (b *BaseClusterFixture) SpawnNode() *cluster.Cluster {
+ node := b.spawnClusterMember()
+ b.members = append(b.members, node)
+ return node
+}
+
+func (b *BaseClusterFixture) RemoveNode(node *cluster.Cluster, graceful bool) {
+ has := false
+ for i, member := range b.members {
+ if member == node {
+ has = true
+ b.members = append(b.members[:i], b.members[i+1:]...)
+ member.Shutdown(graceful)
+ break
+ }
+ }
+ if !has {
+ plog.Error("node not found", log.Object("node", node))
+ }
+}
+
+func (b *BaseClusterFixture) ShutDown() {
+ b.config.OnDeposing()
+ b.waitForMembersToShutdown()
+ b.members = b.members[:0]
+}
+
+// spawnClusterNodes spawns a number of cluster nodes
+func (b *BaseClusterFixture) spawnClusterNodes() []*cluster.Cluster {
+ nodes := make([]*cluster.Cluster, 0, b.clusterSize)
+ for i := 0; i < b.clusterSize; i++ {
+ nodes = append(nodes, b.spawnClusterMember())
+ }
+
+ bgCtx := context.Background()
+ timeoutCtx, cancel := context.WithTimeout(bgCtx, time.Second*10)
+ defer cancel()
+ group := new(errgroup.Group)
+ for _, node := range nodes {
+ tmpNode := node
+ group.Go(func() error {
+ done := make(chan struct{})
+ go func() {
+ tmpNode.MemberList.TopologyConsensus(timeoutCtx)
+ close(done)
+ }()
+
+ select {
+ case <-timeoutCtx.Done():
+ return timeoutCtx.Err()
+ case <-done:
+ return nil
+ }
+ })
+ }
+ err := group.Wait()
+ if err != nil {
+ panic("Failed to reach consensus")
+ }
+
+ return nodes
+}
+
+// spawnClusterMember spawns a cluster members
+func (b *BaseClusterFixture) spawnClusterMember() *cluster.Cluster {
+ config := cluster.Configure(b.clusterName, b.config.GetClusterProvider(), b.config.GetIdentityLookup(b.clusterName),
+ remote.Configure("localhost", 0),
+ cluster.WithKinds(b.config.GetClusterKinds()...),
+ )
+ config = b.config.Configure(config)
+
+ system := actor.NewActorSystem()
+
+ c := cluster.New(system, config)
+ c.StartMember()
+ return c
+}
+
+// waitForMembersToShutdown waits for the members to shutdown
+func (b *BaseClusterFixture) waitForMembersToShutdown() {
+ for _, member := range b.members {
+ plog.Info("Preparing shutdown for cluster member", log.String("member", member.ActorSystem.ID))
+ }
+
+ group := new(errgroup.Group)
+ timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Second*1000)
+ defer cancel()
+
+ for _, member := range b.members {
+ member := member
+ group.Go(func() error {
+ done := make(chan struct{})
+ go func() {
+ plog.Info("Shutting down cluster member", log.String("member", member.ActorSystem.ID))
+ member.Shutdown(true)
+ close(done)
+ }()
+
+ select {
+ case <-timeoutCtx.Done():
+ return timeoutCtx.Err()
+ case <-done:
+ return nil
+ }
+ })
+ }
+ err := group.Wait()
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/cluster/cluster_test_tool/in_memory_cluster_fixture.go b/cluster/cluster_test_tool/in_memory_cluster_fixture.go
new file mode 100644
index 0000000000000000000000000000000000000000..7485be278a6d4cfdc7a92d51b5c8112d7552d6ac
--- /dev/null
+++ b/cluster/cluster_test_tool/in_memory_cluster_fixture.go
@@ -0,0 +1,19 @@
+package cluster_test_tool
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/clusterproviders/test"
+)
+
+// NewBaseInMemoryClusterFixture creates a new in memory cluster fixture
+func NewBaseInMemoryClusterFixture(clusterSize int, opts ...ClusterFixtureOption) *BaseClusterFixture {
+ inMemAgent := test.NewInMemAgent()
+ baseInMemoryOpts := []ClusterFixtureOption{
+ WithGetClusterProvider(func() cluster.ClusterProvider {
+ return test.NewTestProvider(inMemAgent)
+ }),
+ }
+ baseInMemoryOpts = append(baseInMemoryOpts, opts...)
+
+ return NewBaseClusterFixture(clusterSize, baseInMemoryOpts...)
+}
diff --git a/cluster/cluster_test_tool/log.go b/cluster/cluster_test_tool/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..acd7e572b6097eb79390563d8c0541d9ca1cfc7a
--- /dev/null
+++ b/cluster/cluster_test_tool/log.go
@@ -0,0 +1,11 @@
+package cluster_test_tool
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DebugLevel, "[CLUSTER TEST]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/cluster_test_tool/pubsub_cluster.pb.go b/cluster/cluster_test_tool/pubsub_cluster.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..b91d781b7ac900f70398ede8b3b3ee61db2fac1d
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_cluster.pb.go
@@ -0,0 +1,198 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: pubsub_cluster.proto
+
+package cluster_test_tool
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type DataPublished struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Data int32 `protobuf:"varint,1,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *DataPublished) Reset() {
+ *x = DataPublished{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_cluster_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DataPublished) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DataPublished) ProtoMessage() {}
+
+func (x *DataPublished) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_cluster_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DataPublished.ProtoReflect.Descriptor instead.
+func (*DataPublished) Descriptor() ([]byte, []int) {
+ return file_pubsub_cluster_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *DataPublished) GetData() int32 {
+ if x != nil {
+ return x.Data
+ }
+ return 0
+}
+
+type Response struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Response) Reset() {
+ *x = Response{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_cluster_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Response) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Response) ProtoMessage() {}
+
+func (x *Response) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_cluster_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Response.ProtoReflect.Descriptor instead.
+func (*Response) Descriptor() ([]byte, []int) {
+ return file_pubsub_cluster_proto_rawDescGZIP(), []int{1}
+}
+
+var File_pubsub_cluster_proto protoreflect.FileDescriptor
+
+var file_pubsub_cluster_proto_rawDesc = []byte{
+ 0x0a, 0x14, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f,
+ 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6f, 0x6c, 0x22, 0x23, 0x0a, 0x0d, 0x44, 0x61, 0x74,
+ 0x61, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61,
+ 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x0a,
+ 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x2f, 0x67,
+ 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72,
+ 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f,
+ 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
+ 0x5f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6f, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x33,
+}
+
+var (
+ file_pubsub_cluster_proto_rawDescOnce sync.Once
+ file_pubsub_cluster_proto_rawDescData = file_pubsub_cluster_proto_rawDesc
+)
+
+func file_pubsub_cluster_proto_rawDescGZIP() []byte {
+ file_pubsub_cluster_proto_rawDescOnce.Do(func() {
+ file_pubsub_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(file_pubsub_cluster_proto_rawDescData)
+ })
+ return file_pubsub_cluster_proto_rawDescData
+}
+
+var file_pubsub_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_pubsub_cluster_proto_goTypes = []interface{}{
+ (*DataPublished)(nil), // 0: cluster_test_tool.DataPublished
+ (*Response)(nil), // 1: cluster_test_tool.Response
+}
+var file_pubsub_cluster_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_pubsub_cluster_proto_init() }
+func file_pubsub_cluster_proto_init() {
+ if File_pubsub_cluster_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_pubsub_cluster_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DataPublished); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_cluster_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Response); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_pubsub_cluster_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_pubsub_cluster_proto_goTypes,
+ DependencyIndexes: file_pubsub_cluster_proto_depIdxs,
+ MessageInfos: file_pubsub_cluster_proto_msgTypes,
+ }.Build()
+ File_pubsub_cluster_proto = out.File
+ file_pubsub_cluster_proto_rawDesc = nil
+ file_pubsub_cluster_proto_goTypes = nil
+ file_pubsub_cluster_proto_depIdxs = nil
+}
diff --git a/cluster/cluster_test_tool/pubsub_cluster.proto b/cluster/cluster_test_tool/pubsub_cluster.proto
new file mode 100644
index 0000000000000000000000000000000000000000..73d59178ab080f3c274828de0a6164a11a592921
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_cluster.proto
@@ -0,0 +1,9 @@
+syntax = "proto3";
+package cluster_test_tool;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster/cluster_test_tool";
+
+message DataPublished {
+ int32 data = 1;
+}
+
+message Response {}
diff --git a/cluster/cluster_test_tool/pubsub_cluster_fixture.go b/cluster/cluster_test_tool/pubsub_cluster_fixture.go
new file mode 100644
index 0000000000000000000000000000000000000000..bea422779a9c27fd1c3007fb9ff2a1f5dd845561
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_cluster_fixture.go
@@ -0,0 +1,236 @@
+package cluster_test_tool
+
+import (
+ "errors"
+ "math/rand"
+ "strconv"
+ "sync"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/net/context"
+)
+
+const (
+ PubSubSubscriberKind = "Subscriber"
+ PubSubTimeoutSubscriberKind = "TimeoutSubscriber"
+)
+
+type PubSubClusterFixture struct {
+ *BaseClusterFixture
+
+ useDefaultTopicRegistration bool
+ t testing.TB
+
+ Deliveries []Delivery
+ DeliveriesLock *sync.RWMutex
+
+ subscriberStore cluster.KeyValueStore[*cluster.Subscribers]
+}
+
+func NewPubSubClusterFixture(t testing.TB, clusterSize int, useDefaultTopicRegistration bool, opts ...ClusterFixtureOption) *PubSubClusterFixture {
+ lock := &sync.RWMutex{}
+ store := NewInMemorySubscriberStore()
+ fixture := &PubSubClusterFixture{
+ t: t,
+ useDefaultTopicRegistration: useDefaultTopicRegistration,
+ Deliveries: []Delivery{},
+ DeliveriesLock: lock,
+ subscriberStore: store,
+ }
+
+ pubSubOpts := []ClusterFixtureOption{
+ WithGetClusterKinds(func() []*cluster.Kind {
+ kinds := []*cluster.Kind{
+ cluster.NewKind(PubSubSubscriberKind, fixture.subscriberProps()),
+ cluster.NewKind(PubSubTimeoutSubscriberKind, fixture.timeoutSubscriberProps()),
+ }
+ if !fixture.useDefaultTopicRegistration {
+ kinds = append(kinds, cluster.NewKind(cluster.TopicActorKind, actor.PropsFromProducer(func() actor.Actor {
+ return cluster.NewTopicActor(store)
+ })))
+ }
+ return kinds
+ }),
+ WithClusterConfigure(func(config *cluster.Config) *cluster.Config {
+ cluster.WithRequestTimeout(time.Second * 1)(config)
+ cluster.WithPubSubSubscriberTimeout(time.Second * 2)(config)
+ return config
+ }),
+ }
+ pubSubOpts = append(pubSubOpts, opts...)
+
+ fixture.BaseClusterFixture = NewBaseInMemoryClusterFixture(clusterSize, pubSubOpts...)
+ return fixture
+}
+
+func (p *PubSubClusterFixture) RandomMember() *cluster.Cluster {
+ members := p.BaseClusterFixture.GetMembers()
+ return members[rand.Intn(len(members))]
+}
+
+// VerifyAllSubscribersGotAllTheData verifies that all subscribers got all the data
+func (p *PubSubClusterFixture) VerifyAllSubscribersGotAllTheData(subscriberIds []string, numMessages int) {
+ WaitUntil(p.t, func() bool {
+ p.DeliveriesLock.RLock()
+ defer p.DeliveriesLock.RUnlock()
+ return len(p.Deliveries) == numMessages*len(subscriberIds)
+ }, "All messages should be delivered ", DefaultWaitTimeout*1000)
+
+ p.DeliveriesLock.RLock()
+ defer p.DeliveriesLock.RUnlock()
+
+ expected := make([]Delivery, 0, len(subscriberIds))
+ for _, subscriberId := range subscriberIds {
+ for i := 0; i < numMessages; i++ {
+ expected = append(expected, Delivery{
+ Identity: subscriberId,
+ Data: i,
+ })
+ }
+ }
+ assert.ElementsMatch(p.t, expected, p.Deliveries)
+}
+
+// SubscribeAllTo subscribes all the given subscribers to the given topic
+func (p *PubSubClusterFixture) SubscribeAllTo(topic string, subscriberIds []string) {
+ for _, subscriberId := range subscriberIds {
+ p.SubscribeTo(topic, subscriberId, PubSubSubscriberKind)
+ }
+}
+
+// UnSubscribeAllFrom unsubscribes all the given subscribers from the given topic
+func (p *PubSubClusterFixture) UnSubscribeAllFrom(topic string, subscriberIds []string) {
+ for _, subscriberId := range subscriberIds {
+ p.UnSubscribeTo(topic, subscriberId, PubSubSubscriberKind)
+ }
+}
+
+// SubscribeTo subscribes the given subscriber to the given topic
+func (p *PubSubClusterFixture) SubscribeTo(topic, identity, kind string) {
+ c := p.RandomMember()
+ res, err := c.SubscribeByClusterIdentity(topic, cluster.NewClusterIdentity(identity, kind), cluster.WithTimeout(time.Second*5))
+ assert.NoError(p.t, err, kind+"/"+identity+" should be able to subscribe to topic "+topic)
+ assert.NotNil(p.t, res, kind+"/"+identity+" subscribing should not time out on topic "+topic)
+}
+
+// UnSubscribeTo unsubscribes the given subscriber from the given topic
+func (p *PubSubClusterFixture) UnSubscribeTo(topic, identity, kind string) {
+ c := p.RandomMember()
+ res, err := c.UnsubscribeByClusterIdentity(topic, cluster.NewClusterIdentity(identity, kind), cluster.WithTimeout(time.Second*5))
+ assert.NoError(p.t, err, kind+"/"+identity+" should be able to unsubscribe from topic "+topic)
+ assert.NotNil(p.t, res, kind+"/"+identity+" subscribing should not time out on topic "+topic)
+}
+
+// PublishData publishes the given message to the given topic
+func (p *PubSubClusterFixture) PublishData(topic string, data int) (*cluster.PublishResponse, error) {
+ c := p.RandomMember()
+ return c.Publisher().Publish(context.Background(), topic, &DataPublished{Data: int32(data)}, cluster.WithTimeout(time.Second*5))
+}
+
+// PublishDataBatch publishes the given messages to the given topic
+func (p *PubSubClusterFixture) PublishDataBatch(topic string, data []int) (*cluster.PublishResponse, error) {
+ batches := make([]interface{}, 0)
+ for _, d := range data {
+ batches = append(batches, &DataPublished{Data: int32(d)})
+ }
+
+ c := p.RandomMember()
+ return c.Publisher().PublishBatch(context.Background(), topic, &cluster.PubSubBatch{Envelopes: batches}, cluster.WithTimeout(time.Second*5))
+}
+
+// SubscriberIds returns the subscriber ids
+func (p *PubSubClusterFixture) SubscriberIds(prefix string, count int) []string {
+ ids := make([]string, 0, count)
+ for i := 0; i < count; i++ {
+ ids = append(ids, prefix+strconv.Itoa(i))
+ }
+ return ids
+}
+
+// GetSubscribersForTopic returns the subscribers for the given topic
+func (p *PubSubClusterFixture) GetSubscribersForTopic(topic string) (*cluster.Subscribers, error) {
+ return p.subscriberStore.Get(context.Background(), topic)
+}
+
+// ClearDeliveries clears the deliveries
+func (p *PubSubClusterFixture) ClearDeliveries() {
+ p.DeliveriesLock.Lock()
+ defer p.DeliveriesLock.Unlock()
+ p.Deliveries = make([]Delivery, 0)
+}
+
+// subscriberProps returns the props for the subscriber actor
+func (p *PubSubClusterFixture) subscriberProps() *actor.Props {
+ return actor.PropsFromFunc(func(context actor.Context) {
+ if msg, ok := context.Message().(*DataPublished); ok {
+ identity := cluster.GetClusterIdentity(context)
+
+ p.AppendDelivery(Delivery{
+ Identity: identity.Identity,
+ Data: int(msg.Data),
+ })
+ context.Respond(&Response{})
+ }
+ })
+}
+
+// timeoutSubscriberProps returns the props for the subscriber actor
+func (p *PubSubClusterFixture) timeoutSubscriberProps() *actor.Props {
+ return actor.PropsFromFunc(func(context actor.Context) {
+ if msg, ok := context.Message().(*DataPublished); ok {
+ time.Sleep(time.Second * 4) // 4 seconds is longer than the configured subscriber timeout
+
+ identity := cluster.GetClusterIdentity(context)
+ p.AppendDelivery(Delivery{
+ Identity: identity.Identity,
+ Data: int(msg.Data),
+ })
+ context.Respond(&Response{})
+ }
+ })
+}
+
+// AppendDelivery appends a delivery to the deliveries slice
+func (p *PubSubClusterFixture) AppendDelivery(delivery Delivery) {
+ p.DeliveriesLock.Lock()
+ p.Deliveries = append(p.Deliveries, delivery)
+ p.DeliveriesLock.Unlock()
+}
+
+type Delivery struct {
+ Identity string
+ Data int
+}
+
+func NewInMemorySubscriberStore() *InMemorySubscribersStore[*cluster.Subscribers] {
+ return &InMemorySubscribersStore[*cluster.Subscribers]{
+ store: &sync.Map{},
+ }
+}
+
+type InMemorySubscribersStore[T any] struct {
+ store *sync.Map // map[string]T
+}
+
+func (i *InMemorySubscribersStore[T]) Set(_ context.Context, key string, value T) error {
+ i.store.Store(key, value)
+ return nil
+}
+
+func (i *InMemorySubscribersStore[T]) Get(_ context.Context, key string) (T, error) {
+ var r T
+ value, ok := i.store.Load(key)
+ if !ok {
+ return r, errors.New("not found")
+ }
+ return value.(T), nil
+}
+
+func (i *InMemorySubscribersStore[T]) Clear(_ context.Context, key string) error {
+ i.store.Delete(key)
+ return nil
+}
diff --git a/cluster/cluster_test_tool/pubsub_default_registration_test.go b/cluster/cluster_test_tool/pubsub_default_registration_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..51b9ef281c256244a8f7e20420f58acf4df4781e
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_default_registration_test.go
@@ -0,0 +1,44 @@
+package cluster_test_tool
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type PubSubDefaultRegistrationTestSuite struct {
+ suite.Suite
+ fixture *PubSubClusterFixture
+}
+
+func (suite *PubSubDefaultRegistrationTestSuite) SetupTest() {
+ suite.fixture = NewPubSubClusterFixture(suite.T(), 1, true)
+ suite.fixture.Initialize()
+}
+
+func (suite *PubSubDefaultRegistrationTestSuite) TearDownTest() {
+ suite.fixture.ShutDown()
+}
+
+func (suite *PubSubDefaultRegistrationTestSuite) TestPubSubWorksWithDefaultTopicRegistration() {
+ subscriberIds := suite.fixture.SubscriberIds("topic-default", 20)
+ const topic = "topic-default-registration"
+ const numMessage = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ for i := 0; i < numMessage; i++ {
+ data, err := suite.fixture.PublishData(topic, i)
+ suite.Assert().NoError(err, "message "+strconv.Itoa(i)+" should not has error")
+ suite.Assert().NotNil(data, "response "+strconv.Itoa(i)+" should not be nil")
+ }
+
+ suite.fixture.VerifyAllSubscribersGotAllTheData(subscriberIds, numMessage)
+}
+
+// In order for 'go test' to run this suite, we need to create
+// a normal test function and pass our suite to suite.Run
+func TestPubSubDefaultRegistrationTestSuite(t *testing.T) {
+ suite.Run(t, new(PubSubDefaultRegistrationTestSuite))
+}
diff --git a/cluster/cluster_test_tool/pubsub_member_test.go b/cluster/cluster_test_tool/pubsub_member_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..84460512835485c79223bdf1665edefaf19c4e1e
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_member_test.go
@@ -0,0 +1,107 @@
+package cluster_test_tool
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/suite"
+)
+
+type PubSubMemberTestSuite struct {
+ suite.Suite
+ fixture *PubSubClusterFixture
+}
+
+func (suite *PubSubMemberTestSuite) SetupTest() {
+ suite.fixture = NewPubSubClusterFixture(suite.T(), 3, false)
+ suite.fixture.Initialize()
+}
+
+func (suite *PubSubMemberTestSuite) TestWhenMemberLeavesPidSubscribersGetRemovedFromTheSubscriberList() {
+ const topic = "leaving-member"
+
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ if msg, ok := context.Message().(*DataPublished); ok {
+ suite.fixture.AppendDelivery(Delivery{Identity: context.Self().String(), Data: int(msg.Data)})
+ }
+ })
+ // spawn on members
+ members := suite.fixture.GetMembers()
+ leavingMember := members[0]
+ leavingPid := leavingMember.ActorSystem.Root.Spawn(props)
+ stayingMember := members[len(members)-1]
+ stayingPid := stayingMember.ActorSystem.Root.Spawn(props)
+
+ // subscribe by pids
+ _, err := leavingMember.SubscribeByPid(topic, leavingPid)
+ suite.Assert().NoError(err)
+ _, err = stayingMember.SubscribeByPid(topic, stayingPid)
+ suite.Assert().NoError(err)
+
+ // to spice things up, also subscribe virtual actors
+ subscribeIds := suite.fixture.SubscriberIds("leaving", 20)
+ suite.fixture.SubscribeAllTo(topic, subscribeIds)
+
+ // publish data
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err)
+
+ // everyone should have received the data
+ WaitUntil(suite.T(), func() bool {
+ suite.fixture.DeliveriesLock.RLock()
+ defer suite.fixture.DeliveriesLock.RUnlock()
+ return len(suite.fixture.Deliveries) == len(subscribeIds)+2
+ }, "all subscribers should have received the data", DefaultWaitTimeout)
+
+ suite.fixture.DeliveriesLock.RLock()
+ suite.Assert().Equal(len(subscribeIds)+2, len(suite.fixture.Deliveries))
+ suite.fixture.DeliveriesLock.RUnlock()
+
+ suite.fixture.RemoveNode(leavingMember, true)
+
+ WaitUntil(suite.T(), func() bool {
+ blockedOnlyOne := true
+ for _, member := range suite.fixture.GetMembers() {
+ blockList := member.Remote.BlockList()
+ blockedOnlyOne = blockedOnlyOne && blockList.Len() == 1
+ }
+ return blockedOnlyOne
+ }, "Member should leave cluster", DefaultWaitTimeout)
+
+ suite.fixture.ClearDeliveries()
+ _, err = suite.fixture.PublishData(topic, 2)
+ suite.Assert().NoError(err)
+
+ // the failure in delivery caused topic actor to remove subscribers from the member that left
+ // next publish should succeed and deliver to remaining subscribers
+ WaitUntil(suite.T(), func() bool {
+ suite.fixture.DeliveriesLock.RLock()
+ defer suite.fixture.DeliveriesLock.RUnlock()
+ return len(suite.fixture.Deliveries) == len(subscribeIds)+1
+ }, "All subscribers apart the one that left should get the message", DefaultWaitTimeout)
+
+ WaitUntil(suite.T(), func() bool {
+ subscribers, err := suite.fixture.GetSubscribersForTopic(topic)
+ suite.Assert().NoError(err)
+
+ dontContainLeavingMember := true
+ for _, subscriber := range subscribers.Subscribers {
+ pid := subscriber.GetPid()
+ if pid != nil && pid.Address == leavingPid.Address && pid.Id == leavingPid.Id {
+ dontContainLeavingMember = false
+ break
+ }
+ }
+ return dontContainLeavingMember
+ }, "Subscriber that left should be removed from subscribers list", DefaultWaitTimeout)
+}
+
+func (suite *PubSubMemberTestSuite) TearDownTest() {
+ suite.fixture.ShutDown()
+}
+
+// In order for 'go test' to run this suite, we need to create
+// a normal test function and pass our suite to suite.Run
+func TestPubSubMemberTestSuite(t *testing.T) {
+ suite.Run(t, new(PubSubMemberTestSuite))
+}
diff --git a/cluster/cluster_test_tool/pubsub_test.go b/cluster/cluster_test_tool/pubsub_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..09242d25af0846e4de90d8d35210807bf35de4a4
--- /dev/null
+++ b/cluster/cluster_test_tool/pubsub_test.go
@@ -0,0 +1,337 @@
+package cluster_test_tool
+
+import (
+ "context"
+ "strconv"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "github.com/stretchr/testify/suite"
+)
+
+type PubSubTestSuite struct {
+ suite.Suite
+ fixture *PubSubClusterFixture
+}
+
+func (suite *PubSubTestSuite) SetupTest() {
+ suite.fixture = NewPubSubClusterFixture(suite.T(), 2, false)
+ suite.fixture.Initialize()
+}
+
+func (suite *PubSubTestSuite) TearDownTest() {
+ suite.fixture.ShutDown()
+}
+
+func (suite *PubSubTestSuite) TestCanDeliverSingleMessages() {
+ subscriberIds := suite.fixture.SubscriberIds("single-test", 20)
+ const topic = "single-test-topic"
+ const numMessages = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ for i := 0; i < numMessages; i++ {
+ data, err := suite.fixture.PublishData(topic, i)
+ suite.Assert().NoError(err, "message "+strconv.Itoa(i)+" should not has error")
+ suite.Assert().NotNil(data, "response "+strconv.Itoa(i)+" should not be nil")
+ }
+
+ suite.fixture.VerifyAllSubscribersGotAllTheData(subscriberIds, numMessages)
+}
+
+func (suite *PubSubTestSuite) TestCanDeliverMessageBatches() {
+ subscriberIds := suite.fixture.SubscriberIds("batch-test", 20)
+ const topic = "batch-test-topic"
+ const numMessages = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ for i := 0; i < numMessages/10; i++ {
+ data := intRange(i*10, 10)
+ batch, err := suite.fixture.PublishDataBatch(topic, data)
+ suite.Assert().NoError(err, "message "+strconv.Itoa(i)+" should not has error")
+ suite.Assert().NotNil(batch, "response "+strconv.Itoa(i)+" should not be nil")
+ }
+ suite.fixture.VerifyAllSubscribersGotAllTheData(subscriberIds, numMessages)
+}
+
+func (suite *PubSubTestSuite) TestUnsubscribedActorDoesNotReceiveMessages() {
+ const sub1 = "unsubscribe-test-1"
+ const sub2 = "unsubscribe-test-2"
+ const topic = "unsubscribe-test"
+
+ suite.fixture.SubscribeTo(topic, sub1, PubSubSubscriberKind)
+ suite.fixture.SubscribeTo(topic, sub2, PubSubSubscriberKind)
+
+ suite.fixture.UnSubscribeTo(topic, sub2, PubSubSubscriberKind)
+
+ _, err := suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ time.Sleep(time.Second * 1) // give time for the message "not to be delivered" to second subscriber
+ WaitUntil(suite.T(), func() bool {
+ suite.fixture.DeliveriesLock.RLock()
+ defer suite.fixture.DeliveriesLock.RUnlock()
+ return len(suite.fixture.Deliveries) == 1
+ }, "only one delivery should happen because the other actor is unsubscribed", DefaultWaitTimeout)
+
+ suite.fixture.DeliveriesLock.RLock()
+ defer suite.fixture.DeliveriesLock.RUnlock()
+ suite.Assert().Len(suite.fixture.Deliveries, 1, "only one delivery should happen because the other actor is unsubscribed")
+ suite.Assert().Equal(sub1, suite.fixture.Deliveries[0].Identity, "the other actor should be unsubscribed")
+}
+
+func (suite *PubSubTestSuite) TestCanSubscribeWithPid() {
+ const topic = "pid-subscribe"
+
+ var deliveredMessage *DataPublished
+
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *DataPublished:
+ deliveredMessage = msg
+ }
+ })
+ member := suite.fixture.GetMembers()[0]
+ pid := member.ActorSystem.Root.Spawn(props)
+ _, err := member.SubscribeByPid(topic, pid)
+ suite.Assert().NoError(err, "SubscribeByPid should not has error")
+
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ WaitUntil(suite.T(), func() bool {
+ return deliveredMessage != nil
+ }, "message should be delivered", DefaultWaitTimeout)
+ suite.Assert().EqualValues(1, deliveredMessage.Data)
+}
+
+func (suite *PubSubTestSuite) TestCanUnsubscribeWithPid() {
+ const topic = "pid-unsubscribe"
+
+ var deliveryCount int32 = 0
+
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch context.Message().(type) {
+ case *DataPublished:
+ atomic.AddInt32(&deliveryCount, 1)
+ }
+ })
+ member := suite.fixture.GetMembers()[0]
+ pid := member.ActorSystem.Root.Spawn(props)
+ _, err := member.SubscribeByPid(topic, pid)
+ suite.Assert().NoError(err, "SubscribeByPid should not has error")
+
+ _, err = member.UnsubscribeByPid(topic, pid)
+ suite.Assert().NoError(err, "UnsubscribeByPid should not has error")
+
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ time.Sleep(time.Second * 1) // give time for the message "not to be delivered" to second subscriber
+ suite.Assert().EqualValues(0, deliveryCount, "message should not be delivered")
+}
+
+func (suite *PubSubTestSuite) TestStoppedActorThatDidNotUnsubscribeDoesNotBlockPublishingToTopic() {
+ const topic = "missing-unsubscribe"
+ var deliveryCount int32 = 0
+
+ // this scenario is only relevant for regular actors,
+ // virtual actors always exist, so the msgs should never be deadlettered
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ switch context.Message().(type) {
+ case *DataPublished:
+ atomic.AddInt32(&deliveryCount, 1)
+ }
+ })
+ member := suite.fixture.GetMembers()[0]
+ pid1 := member.ActorSystem.Root.Spawn(props)
+ pid2 := member.ActorSystem.Root.Spawn(props)
+
+ // spawn two actors and subscribe them to the topic
+ _, err := member.SubscribeByPid(topic, pid1)
+ suite.Assert().NoError(err, "SubscribeByPid1 should not has error")
+ _, err = member.SubscribeByPid(topic, pid2)
+ suite.Assert().NoError(err, "SubscribeByPid2 should not has error")
+
+ // publish one message
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ WaitUntil(suite.T(), func() bool {
+ return atomic.LoadInt32(&deliveryCount) == 2
+ }, "both messages should be delivered", DefaultWaitTimeout)
+
+ // kill one of the actors
+ member.ActorSystem.Root.Stop(pid2)
+
+ // publish again
+ _, err = suite.fixture.PublishData(topic, 2)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ WaitUntil(suite.T(), func() bool {
+ return atomic.LoadInt32(&deliveryCount) == 3
+ }, "second publish should be delivered only to one of the actors", DefaultWaitTimeout)
+
+ WaitUntil(suite.T(), func() bool {
+ subscribers, err := suite.fixture.GetSubscribersForTopic(topic)
+ suite.Assert().NoError(err, "GetSubscribersForTopic should not has error")
+
+ hasPid2 := false
+ for _, subscriber := range subscribers.Subscribers {
+ if subscriber.GetPid() != nil &&
+ subscriber.GetPid().Id == pid2.Id &&
+ subscriber.GetPid().Address == pid2.Address {
+ hasPid2 = true
+ break
+ }
+ }
+ return !hasPid2
+ }, "pid2 should be removed from subscriber store", DefaultWaitTimeout*1000)
+}
+
+func (suite *PubSubTestSuite) TestSlowPidSubscriberThatTimesOutDoesNotPreventSubsequentPublishes() {
+ const topic = "slow-pid-subscriber"
+ var deliveryCount int32 = 0
+
+ // a slow subscriber that will timeout
+ props := actor.PropsFromFunc(func(context actor.Context) {
+ time.Sleep(time.Second * 4)
+ atomic.AddInt32(&deliveryCount, 1)
+ })
+
+ member := suite.fixture.RandomMember()
+ pid := member.ActorSystem.Root.Spawn(props)
+ _, err := member.SubscribeByPid(topic, pid)
+ suite.Assert().NoError(err, "SubscribeByPid should not has error")
+
+ // publish one message
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ // next published message should also be delivered
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData should not has error")
+
+ WaitUntil(suite.T(), func() bool {
+ return atomic.LoadInt32(&deliveryCount) == 2
+ }, "A timing out subscriber should not prevent subsequent publishes", time.Second*10)
+}
+
+func (suite *PubSubTestSuite) TestSlowClusterIdentitySubscriberThatTimesOutDoesNotPreventSubsequentPublishes() {
+ const topic = "slow-ci-subscriber"
+ suite.fixture.SubscribeTo(topic, "slow-ci-1", PubSubTimeoutSubscriberKind)
+
+ // publish one message
+ _, err := suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData1 should not has error")
+
+ // next published message should also be delivered
+ _, err = suite.fixture.PublishData(topic, 1)
+ suite.Assert().NoError(err, "PublishData2 should not has error")
+
+ WaitUntil(suite.T(), func() bool {
+ suite.fixture.DeliveriesLock.RLock()
+ defer suite.fixture.DeliveriesLock.RUnlock()
+
+ return len(suite.fixture.Deliveries) == 2
+ }, "A timing out subscriber should not prevent subsequent publishes", time.Second*10)
+}
+
+func (suite *PubSubTestSuite) TestCanPublishMessagesViaBatchingProducer() {
+ subscriberIds := suite.fixture.SubscriberIds("batching-producer-test", 20)
+ const topic = "batching-producer"
+ const numMessages = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ producer := suite.fixture.GetMembers()[0].BatchingProducer(topic, cluster.WithBatchingProducerBatchSize(10))
+ defer producer.Dispose()
+
+ wg := sync.WaitGroup{}
+ for i := 0; i < numMessages; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ _, err := producer.Produce(context.Background(), &DataPublished{Data: int32(i)})
+ suite.Assert().NoError(err, "Produce should not has error")
+ }(i)
+ }
+ wg.Wait()
+
+ suite.fixture.VerifyAllSubscribersGotAllTheData(subscriberIds, numMessages)
+}
+
+func (suite *PubSubTestSuite) TestCanPublishMessagesViaBatchingProducerWithCustomQueue() {
+ subscriberIds := suite.fixture.SubscriberIds("batching-producer-test-with-chan", 20)
+ const topic = "batching-producer-with-chan"
+ const numMessages = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ producer := suite.fixture.GetMembers()[0].BatchingProducer(topic, cluster.WithBatchingProducerBatchSize(10), cluster.WithBatchingProducerMaxQueueSize(2000))
+ defer producer.Dispose()
+
+ wg := sync.WaitGroup{}
+ for i := 0; i < numMessages; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ _, err := producer.Produce(context.Background(), &DataPublished{Data: int32(i)})
+ suite.Assert().NoError(err, "Produce should not has error")
+ }(i)
+ }
+ wg.Wait()
+
+ suite.fixture.VerifyAllSubscribersGotAllTheData(subscriberIds, numMessages)
+}
+
+func (suite *PubSubTestSuite) TestWillExpireTopicActorAfterIdle() {
+ subscriberIds := suite.fixture.SubscriberIds("batching-producer-idl-test", 20)
+ const topic = "batching-producer"
+ const numMessages = 100
+
+ suite.fixture.SubscribeAllTo(topic, subscriberIds)
+
+ firstCluster := suite.fixture.GetMembers()[0]
+
+ producer := firstCluster.BatchingProducer(topic, cluster.WithBatchingProducerPublisherIdleTimeout(time.Second*2))
+ defer producer.Dispose()
+
+ wg := sync.WaitGroup{}
+ for i := 0; i < numMessages; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ _, err := producer.Produce(context.Background(), &DataPublished{Data: int32(i)})
+ suite.Assert().NoError(err, "Produce should not has error")
+ }(i)
+ }
+ wg.Wait()
+
+ pid := firstCluster.Get(topic, cluster.TopicActorKind)
+ suite.Assert().NotNil(pid, "Topic actor should not be nil")
+
+ time.Sleep(time.Second * 5)
+
+ newPid := firstCluster.Get(topic, cluster.TopicActorKind)
+ suite.Assert().NotEqual(pid.String(), newPid.String(), "Topic actor should be recreated")
+}
+
+// In order for 'go test' to run this suite, we need to create
+// a normal test function and pass our suite to suite.Run
+func TestPubSubTestSuite(t *testing.T) {
+ suite.Run(t, new(PubSubTestSuite))
+}
+
+func intRange(start int, count int) []int {
+ res := make([]int, count)
+ for i := 0; i < count; i++ {
+ res[i] = start + i
+ }
+ return res
+}
diff --git a/cluster/cluster_test_tool/wait_helper.go b/cluster/cluster_test_tool/wait_helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..058bcb98279ce7d87ba30c2f58255b09d953a392
--- /dev/null
+++ b/cluster/cluster_test_tool/wait_helper.go
@@ -0,0 +1,26 @@
+package cluster_test_tool
+
+import (
+ "runtime/debug"
+ "testing"
+ "time"
+)
+
+const DefaultWaitTimeout = time.Second * 5
+
+func WaitUntil(t testing.TB, cond func() bool, errorMsg string, timeout time.Duration) {
+ after := time.After(timeout)
+
+ for {
+ select {
+ case <-after:
+ t.Error(errorMsg)
+ debug.PrintStack()
+ default:
+ if cond() {
+ return
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+}
diff --git a/cluster/clusterproviders/automanaged/automanaged.go b/cluster/clusterproviders/automanaged/automanaged.go
new file mode 100644
index 0000000000000000000000000000000000000000..02bdf35867cee8aa3ecdfeed9661d92ec4539cc2
--- /dev/null
+++ b/cluster/clusterproviders/automanaged/automanaged.go
@@ -0,0 +1,384 @@
+package automanaged
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "net/http"
+ "sync"
+ "time"
+
+ "golang.org/x/net/context"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/labstack/echo"
+ "golang.org/x/sync/errgroup"
+)
+
+// TODO: needs to be attached to the provider instance
+var (
+ clusterTTLErrorMutex = new(sync.Mutex)
+ clusterMonitorErrorMutex = new(sync.Mutex)
+ shutdownMutex = new(sync.Mutex)
+ deregisteredMutex = new(sync.Mutex)
+ activeProviderMutex = new(sync.Mutex)
+ activeProviderRunningMutex = new(sync.Mutex)
+)
+
+type AutoManagedProvider struct {
+ deregistered bool
+ shutdown bool
+ activeProvider *echo.Echo
+ activeProviderRunning bool
+ activeProviderTesting bool
+ httpClient *http.Client
+ monitoringStatus bool
+ clusterName string
+ address string
+ autoManagePort int
+ memberPort int
+ knownKinds []string
+ knownNodes []*NodeModel
+ hosts []string
+ refreshTTL time.Duration
+ clusterTTLError error
+ clusterMonitorError error
+ cluster *cluster.Cluster
+}
+
+// New creates a AutoManagedProvider that connects locally
+func New() *AutoManagedProvider {
+ return NewWithConfig(
+ 2*time.Second,
+ 6330,
+ "localhost:6330",
+ )
+}
+
+// NewWithConfig creates an Automanaged Provider that connects to an all the hosts
+func NewWithConfig(refreshTTL time.Duration, autoManPort int, hosts ...string) *AutoManagedProvider {
+ transport := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 5 * time.Second,
+ KeepAlive: 5 * time.Second,
+ }).DialContext,
+ MaxIdleConns: 10,
+ IdleConnTimeout: 90 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ MaxConnsPerHost: 10,
+ }
+
+ httpClient := &http.Client{
+ Transport: transport,
+ Timeout: 2 * time.Second,
+ }
+
+ p := &AutoManagedProvider{
+ hosts: hosts,
+ httpClient: httpClient,
+ refreshTTL: refreshTTL,
+ autoManagePort: autoManPort,
+ activeProviderRunning: false,
+ monitoringStatus: false,
+ }
+
+ return p
+}
+
+// NewWithTesting creates a testable provider
+func NewWithTesting(refreshTTL time.Duration, autoManPort int, activeProvider *echo.Echo, hosts ...string) *AutoManagedProvider {
+ p := NewWithConfig(refreshTTL, autoManPort, hosts...)
+ p.activeProviderTesting = true
+ p.activeProvider = activeProvider
+ return p
+}
+
+func (p *AutoManagedProvider) init(cluster *cluster.Cluster) error {
+ host, port, err := cluster.ActorSystem.GetHostPort()
+ if err != nil {
+ return err
+ }
+
+ p.clusterName = cluster.Config.Name
+ p.address = host
+ p.memberPort = port
+ p.knownKinds = cluster.GetClusterKinds()
+ p.deregistered = false
+ p.shutdown = false
+ p.cluster = cluster
+ return nil
+}
+
+func (p *AutoManagedProvider) StartMember(cluster *cluster.Cluster) error {
+ if err := p.init(cluster); err != nil {
+ return err
+ }
+ p.UpdateTTL()
+ p.monitorMemberStatusChanges()
+ return nil
+}
+
+func (p *AutoManagedProvider) StartClient(cluster *cluster.Cluster) error {
+ if err := p.init(cluster); err != nil {
+ return err
+ }
+ // p.UpdateTTL()
+ p.monitorMemberStatusChanges()
+ return nil
+}
+
+// DeregisterMember set the shutdown to true preventing anymore TTL updates
+func (p *AutoManagedProvider) DeregisterMember() error {
+ deregisteredMutex.Lock()
+ defer deregisteredMutex.Unlock()
+
+ p.deregistered = true
+ return nil
+}
+
+// Shutdown set the shutdown to true preventing anymore TTL updates
+func (p *AutoManagedProvider) Shutdown(graceful bool) error {
+ shutdownMutex.Lock()
+ defer shutdownMutex.Unlock()
+
+ p.shutdown = true
+ p.activeProvider.Close()
+ return nil
+}
+
+// UpdateTTL sets up an endpoint to respond to other members
+func (p *AutoManagedProvider) UpdateTTL() {
+ activeProviderRunningMutex.Lock()
+ running := p.activeProviderRunning
+ activeProviderRunningMutex.Unlock()
+
+ if (p.isShutdown() || p.isDeregistered()) && running {
+ p.activeProvider.Close()
+ return
+ }
+
+ if running {
+ return
+ }
+
+ // it's not running, and it's not shutdown or de-registered
+ // it's also not a test (this should be refactored)
+
+ if !p.activeProviderTesting {
+ p.activeProvider = echo.New()
+ p.activeProvider.HideBanner = true
+ p.activeProvider.GET("/_health", func(context echo.Context) error {
+ return context.JSON(http.StatusOK, p.getCurrentNode())
+ })
+ }
+ go func() {
+ activeProviderRunningMutex.Lock()
+ p.activeProviderRunning = true
+ activeProviderRunningMutex.Unlock()
+
+ appURI := fmt.Sprintf("0.0.0.0:%d", p.autoManagePort)
+ plog.Error("Automanaged server stopping..!", log.Error(p.activeProvider.Start(appURI)))
+
+ activeProviderRunningMutex.Lock()
+ p.activeProviderRunning = false
+ activeProviderRunningMutex.Unlock()
+ }()
+}
+
+// MonitorMemberStatusChanges creates a go routine that continuously checks other members
+func (p *AutoManagedProvider) monitorMemberStatusChanges() {
+ if !p.monitoringStatus {
+ go func() {
+ for !p.isShutdown() && !p.isDeregistered() {
+ p.monitorStatuses()
+ }
+ }()
+ }
+ p.monitoringStatus = true
+}
+
+// GetHealthStatus returns an error if the cluster health status has problems
+func (p *AutoManagedProvider) GetHealthStatus() error {
+ var err error
+ clusterTTLErrorMutex.Lock()
+ clusterMonitorErrorMutex.Lock()
+ defer clusterMonitorErrorMutex.Unlock()
+ defer clusterTTLErrorMutex.Unlock()
+
+ if p.clusterTTLError != nil {
+ err = fmt.Errorf("TTL: %s", p.clusterTTLError.Error())
+ }
+
+ if p.clusterMonitorError != nil {
+ if err != nil {
+ err = fmt.Errorf("%s - Monitor: %s", err.Error(), p.clusterMonitorError.Error())
+ } else {
+ err = fmt.Errorf("monitor: %s", p.clusterMonitorError.Error())
+ }
+ }
+
+ return err
+}
+
+//
+// Private methods
+//
+
+// monitorStatuses checks for node changes in the cluster
+func (p *AutoManagedProvider) monitorStatuses() {
+ clusterMonitorErrorMutex.Lock()
+ defer clusterMonitorErrorMutex.Unlock()
+
+ autoManagedNodes, err := p.checkNodes()
+ if err != nil && len(autoManagedNodes) == 0 {
+ plog.Error("Failure reaching nodes", log.Error(err))
+ p.clusterMonitorError = err
+ time.Sleep(p.refreshTTL)
+ return
+ }
+ // we should probably check if the cluster needs to be updated.
+ var members []*cluster.Member
+ var newNodes []*NodeModel
+ for _, node := range autoManagedNodes {
+ if node == nil || node.ClusterName != p.clusterName {
+ continue
+ }
+ ms := &cluster.Member{
+ Id: node.ID,
+ Host: node.Address,
+ Port: int32(node.Port),
+ Kinds: node.Kinds,
+ }
+ members = append(members, ms)
+ newNodes = append(newNodes, node)
+ }
+
+ p.knownNodes = newNodes
+ p.clusterMonitorError = nil
+ // publish the current cluster topology onto the event stream
+ p.cluster.MemberList.UpdateClusterTopology(members)
+ time.Sleep(p.refreshTTL)
+}
+
+// checkNodes pings all the nodes and returns the new cluster topology
+func (p *AutoManagedProvider) checkNodes() ([]*NodeModel, error) {
+ allNodes := make([]*NodeModel, len(p.hosts))
+ g, _ := errgroup.WithContext(context.Background())
+
+ for indice, nodeHost := range p.hosts {
+ idx, el := indice, nodeHost // https://golang.org/doc/faq#closures_and_goroutines
+
+ // Calling go funcs to execute the node check
+ g.Go(func() error {
+ url := fmt.Sprintf("http://%s/_health", el)
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ plog.Error("Couldn't request node health status", log.Error(err), log.String("autoManMemberUrl", url))
+ return err
+ }
+
+ resp, err := p.httpClient.Do(req)
+ if err != nil {
+ plog.Error("Bad connection to the node health status", log.Error(err), log.String("autoManMemberUrl", url))
+ return err
+ }
+
+ defer resp.Body.Close() // nolint: errcheck
+
+ if resp.StatusCode != http.StatusOK {
+ err = fmt.Errorf("non 200 status returned: %d - from node: %s", resp.StatusCode, el)
+ plog.Error("Bad response from the node health status", log.Error(err), log.String("autoManMemberUrl", url))
+ return err
+ }
+
+ var node *NodeModel
+ err = json.NewDecoder(resp.Body).Decode(&node)
+ if err != nil {
+ err = fmt.Errorf("could not deserialize response: %v - from node: %s", resp, el)
+ plog.Error("Bad data from the node health status", log.Error(err), log.String("autoManMemberUrl", url))
+ return err
+ }
+
+ allNodes[idx] = node
+ return nil
+ })
+ }
+
+ // waits until all functions have returned
+ err := g.Wait()
+ var retNodes []*NodeModel
+
+ // clear out the nil ones
+ for _, node := range allNodes {
+ if node != nil {
+ retNodes = append(retNodes, node)
+ }
+ }
+
+ return retNodes, err
+}
+
+func (p *AutoManagedProvider) deregisterService() {
+ deregisteredMutex.Lock()
+ defer deregisteredMutex.Unlock()
+
+ p.deregistered = true
+}
+
+func (p *AutoManagedProvider) startActiveProvider() {
+ activeProviderRunningMutex.Lock()
+ running := p.activeProviderRunning
+ activeProviderRunningMutex.Unlock()
+
+ if !running {
+ if !p.activeProviderTesting {
+ p.activeProvider = echo.New()
+ p.activeProvider.HideBanner = true
+ p.activeProvider.GET("/_health", func(context echo.Context) error {
+ return context.JSON(http.StatusOK, p.getCurrentNode())
+ })
+ }
+
+ appURI := fmt.Sprintf("0.0.0.0:%d", p.autoManagePort)
+
+ go func() {
+ activeProviderRunningMutex.Lock()
+ p.activeProviderRunning = true
+ activeProviderRunningMutex.Unlock()
+
+ plog.Error("Automanaged server stopping..!", log.Error(p.activeProvider.Start(appURI)))
+
+ activeProviderRunningMutex.Lock()
+ p.activeProviderRunning = false
+ activeProviderRunningMutex.Unlock()
+ }()
+ }
+}
+
+func (p *AutoManagedProvider) stopActiveProvider() {
+ p.activeProvider.Close()
+}
+
+func (p *AutoManagedProvider) isShutdown() bool {
+ shutdownMutex.Lock()
+ defer shutdownMutex.Unlock()
+ return p.shutdown
+}
+
+func (p *AutoManagedProvider) isDeregistered() bool {
+ deregisteredMutex.Lock()
+ defer deregisteredMutex.Unlock()
+ return p.deregistered
+}
+
+func (p *AutoManagedProvider) isActiveProviderRunning() bool {
+ activeProviderRunningMutex.Lock()
+ defer activeProviderRunningMutex.Unlock()
+ return p.activeProviderRunning
+}
+
+func (p *AutoManagedProvider) getCurrentNode() *NodeModel {
+ return NewNode(p.clusterName, p.cluster.ActorSystem.ID, p.address, p.memberPort, p.autoManagePort, p.knownKinds)
+}
diff --git a/cluster/clusterproviders/automanaged/automanaged_test.go b/cluster/clusterproviders/automanaged/automanaged_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a729533473ec12ec39c0ddd020c1924f470ba56a
--- /dev/null
+++ b/cluster/clusterproviders/automanaged/automanaged_test.go
@@ -0,0 +1 @@
+package automanaged
diff --git a/cluster/clusterproviders/automanaged/log.go b/cluster/clusterproviders/automanaged/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..33cc82640c1ecbb89ae55f38508e446fbdccd7bd
--- /dev/null
+++ b/cluster/clusterproviders/automanaged/log.go
@@ -0,0 +1,11 @@
+package automanaged
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DebugLevel, "[AUTOMANAGED]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/automanaged/member_list_broadcast_test.go b/cluster/clusterproviders/automanaged/member_list_broadcast_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ecb342b0a465cff6e1ee135416ed327a67aae8e
--- /dev/null
+++ b/cluster/clusterproviders/automanaged/member_list_broadcast_test.go
@@ -0,0 +1,75 @@
+package automanaged
+
+import (
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMemberList_Broadcast(t *testing.T) {
+ c := startNode()
+ defer c.Shutdown(true)
+
+ var receivedEvent *cluster.GrainRequest
+
+ for i := 1; i < 20; i++ { // retry several times as we don't know when the cluster will be ready
+ var ok bool
+ if receivedEvent, ok = trySendAndReceiveMessage(t, c, 0xBEEF); ok {
+ break
+ }
+ }
+
+ assert.Equal(t, int32(0xBEEF), receivedEvent.MethodIndex)
+}
+
+func startNode() *cluster.Cluster {
+ system := actor.NewActorSystem()
+
+ provider := New()
+ config := remote.Configure("localhost", 0)
+
+ lookup := disthash.New()
+ clusterConfig := cluster.Configure("my-cluster", provider, lookup, config)
+ cluster := cluster.New(system, clusterConfig)
+
+ cluster.StartMember()
+
+ return cluster
+}
+
+func subscribe(c *cluster.Cluster) (events <-chan *cluster.GrainRequest, cancel func()) {
+ eventChan := make(chan *cluster.GrainRequest, 1)
+
+ subscription := c.ActorSystem.EventStream.Subscribe(func(evt interface{}) {
+ if event, ok := evt.(*cluster.GrainRequest); ok {
+ eventChan <- event
+ }
+ })
+
+ return eventChan, func() { c.ActorSystem.EventStream.Unsubscribe(subscription) }
+}
+
+func trySendAndReceiveMessage(t *testing.T, c *cluster.Cluster, methodIndex int32) (receivedEvent *cluster.GrainRequest, ok bool) {
+ events, cancel := subscribe(c)
+
+ time.Sleep(500 * time.Millisecond)
+
+ c.MemberList.BroadcastEvent(&cluster.GrainRequest{MethodIndex: methodIndex}, true)
+
+ select {
+ case receivedEvent = <-events:
+ ok = true
+ case <-time.After(1 * time.Second):
+ t.Error("Timed out waiting for the event to arrive")
+ }
+
+ cancel()
+
+ return
+}
diff --git a/cluster/clusterproviders/automanaged/node_model.go b/cluster/clusterproviders/automanaged/node_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..83cf45a7217475e066d038954bd745a62c1a4a2d
--- /dev/null
+++ b/cluster/clusterproviders/automanaged/node_model.go
@@ -0,0 +1,23 @@
+package automanaged
+
+// NodeModel represents a node in the cluster
+type NodeModel struct {
+ ID string `json:"id"`
+ Address string `json:"address"`
+ AutoManagePort int `json:"auto_manage_port"`
+ Port int `json:"port"`
+ Kinds []string `json:"kinds"`
+ ClusterName string `json:"cluster_name"`
+}
+
+// NewNode returns a new node for the cluster
+func NewNode(clusterName string, id string, address string, port int, autoManPort int, kind []string) *NodeModel {
+ return &NodeModel{
+ ID: id,
+ ClusterName: clusterName,
+ Address: address,
+ Port: port,
+ AutoManagePort: autoManPort,
+ Kinds: kind,
+ }
+}
diff --git a/cluster/clusterproviders/consul/consul_provider.go b/cluster/clusterproviders/consul/consul_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..814fdfcc1e759383b68805da6757fb4692e7fff2
--- /dev/null
+++ b/cluster/clusterproviders/consul/consul_provider.go
@@ -0,0 +1,206 @@
+package consul
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/hashicorp/consul/api"
+)
+
+var ProviderShuttingDownError = fmt.Errorf("consul cluster provider is shutting down")
+
+type Provider struct {
+ cluster *cluster.Cluster
+ deregistered bool
+ shutdown bool
+ id string
+ clusterName string
+ address string
+ port int
+ knownKinds []string
+ index uint64 // consul blocking index
+ client *api.Client
+ ttl time.Duration
+ refreshTTL time.Duration
+ updateTTLWaitGroup sync.WaitGroup
+ deregisterCritical time.Duration
+ blockingWaitTime time.Duration
+ clusterError error
+ pid *actor.PID
+ consulConfig *api.Config
+}
+
+func New(opts ...Option) (*Provider, error) {
+ return NewWithConfig(&api.Config{}, opts...)
+}
+
+func NewWithConfig(consulConfig *api.Config, opts ...Option) (*Provider, error) {
+ client, err := api.NewClient(consulConfig)
+ if err != nil {
+ return nil, err
+ }
+ p := &Provider{
+ client: client,
+ ttl: 3 * time.Second,
+ refreshTTL: 1 * time.Second,
+ deregisterCritical: 60 * time.Second,
+ blockingWaitTime: 20 * time.Second,
+ consulConfig: consulConfig,
+ }
+ for _, opt := range opts {
+ opt(p)
+ }
+ return p, nil
+}
+
+func (p *Provider) init(c *cluster.Cluster) error {
+ knownKinds := c.GetClusterKinds()
+ clusterName := c.Config.Name
+ memberId := c.ActorSystem.ID
+
+ host, port, err := c.ActorSystem.GetHostPort()
+ if err != nil {
+ return err
+ }
+
+ p.cluster = c
+ p.id = memberId
+ p.clusterName = clusterName
+ p.address = host
+ p.port = port
+ p.knownKinds = knownKinds
+ return nil
+}
+
+func (p *Provider) StartMember(c *cluster.Cluster) error {
+ err := p.init(c)
+ if err != nil {
+ return err
+ }
+
+ p.pid, err = c.ActorSystem.Root.SpawnNamed(actor.PropsFromProducer(func() actor.Actor {
+ return newProviderActor(p)
+ }), "consul-provider")
+ if err != nil {
+ plog.Error("Failed to start consul-provider actor", log.Error(err))
+ return err
+ }
+
+ return nil
+}
+
+func (p *Provider) StartClient(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+ p.blockingStatusChange()
+ p.monitorMemberStatusChanges()
+ return nil
+}
+
+func (p *Provider) DeregisterMember() error {
+ err := p.deregisterService()
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+ p.deregistered = true
+ return nil
+}
+
+func (p *Provider) Shutdown(graceful bool) error {
+ if p.shutdown {
+ return nil
+ }
+ p.shutdown = true
+ if p.pid != nil {
+ if err := p.cluster.ActorSystem.Root.StopFuture(p.pid).Wait(); err != nil {
+ plog.Error("Failed to stop consul-provider actor", log.Error(err))
+ }
+ p.pid = nil
+ }
+
+ return nil
+}
+
+func blockingUpdateTTL(p *Provider) error {
+ p.clusterError = p.client.Agent().UpdateTTL("service:"+p.id, "", api.HealthPassing)
+ return p.clusterError
+}
+
+func (p *Provider) registerService() error {
+ s := &api.AgentServiceRegistration{
+ ID: p.id,
+ Name: p.clusterName,
+ Tags: p.knownKinds,
+ Address: p.address,
+ Port: p.port,
+ Meta: map[string]string{
+ "id": p.id,
+ },
+ Check: &api.AgentServiceCheck{
+ DeregisterCriticalServiceAfter: p.deregisterCritical.String(),
+ TTL: p.ttl.String(),
+ },
+ }
+ return p.client.Agent().ServiceRegister(s)
+}
+
+func (p *Provider) deregisterService() error {
+ return p.client.Agent().ServiceDeregister(p.id)
+}
+
+// call this directly after registering the service
+func (p *Provider) blockingStatusChange() {
+ p.notifyStatuses()
+}
+
+func (p *Provider) notifyStatuses() {
+ statuses, meta, err := p.client.Health().Service(p.clusterName, "", false, &api.QueryOptions{
+ WaitIndex: p.index,
+ WaitTime: p.blockingWaitTime,
+ })
+ plog.Info("Consul health check")
+
+ if err != nil {
+ plog.Error("notifyStatues", log.Error(err))
+ return
+ }
+ p.index = meta.LastIndex
+
+ var members []*cluster.Member
+ for _, v := range statuses {
+ if len(v.Checks) > 0 && v.Checks.AggregatedStatus() == api.HealthPassing {
+ memberId := v.Service.Meta["id"]
+ if memberId == "" {
+ memberId = fmt.Sprintf("%v@%v:%v", p.clusterName, v.Service.Address, v.Service.Port)
+ plog.Info("meta['id'] was empty, fixeds", log.String("id", memberId))
+ }
+ members = append(members, &cluster.Member{
+ Id: memberId,
+ Host: v.Service.Address,
+ Port: int32(v.Service.Port),
+ Kinds: v.Service.Tags,
+ })
+ }
+ }
+ // the reason why we want this in a batch and not as individual messages is that
+ // if we have an atomic batch, we can calculate what nodes have left the cluster
+ // passing events one by one, we can't know if someone left or just haven't changed status for a long time
+
+ // publish the current cluster topology onto the event stream
+ p.cluster.MemberList.UpdateClusterTopology(members)
+}
+
+func (p *Provider) monitorMemberStatusChanges() {
+ go func() {
+ for !p.shutdown {
+ p.notifyStatuses()
+ }
+ }()
+}
diff --git a/cluster/clusterproviders/consul/consul_provider_test.go b/cluster/clusterproviders/consul/consul_provider_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..48d3ac3c1068674b97909f4b58e0074bb3064b53
--- /dev/null
+++ b/cluster/clusterproviders/consul/consul_provider_test.go
@@ -0,0 +1,180 @@
+package consul
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/assert"
+)
+
+func newClusterForTest(name string, addr string, cp cluster.ClusterProvider) *cluster.Cluster {
+ host, _port, err := net.SplitHostPort(addr)
+ if err != nil {
+ panic(err)
+ }
+ port, _ := strconv.Atoi(_port)
+ remoteConfig := remote.Configure(host, port)
+ lookup := disthash.New()
+ config := cluster.Configure(name, cp, lookup, remoteConfig)
+ // return cluster.NewForTest(system, config)
+
+ system := actor.NewActorSystem()
+ c := cluster.New(system, config)
+
+ // use for test without start remote
+ c.ActorSystem.ProcessRegistry.Address = addr
+ c.MemberList = cluster.NewMemberList(c)
+ c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
+ return c
+}
+
+func TestStartMember(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ a := assert.New(t)
+
+ p, _ := New()
+ defer p.Shutdown(true)
+
+ c := newClusterForTest("mycluster", "127.0.0.1:8000", p)
+ eventstream := c.ActorSystem.EventStream
+ ch := make(chan interface{}, 16)
+ eventstream.Subscribe(func(m interface{}) {
+ if _, ok := m.(*cluster.ClusterTopology); ok {
+ ch <- m
+ }
+ })
+
+ err := p.StartMember(c)
+ a.NoError(err)
+
+ select {
+ case <-time.After(10 * time.Second):
+ a.FailNow("no member joined yet")
+
+ case m := <-ch:
+ msg := m.(*cluster.ClusterTopology)
+ // member joined
+ members := []*cluster.Member{
+ {
+ // Id: "mycluster@127.0.0.1:8000",
+ Id: fmt.Sprintf("%s", c.ActorSystem.ID),
+ Host: "127.0.0.1",
+ Port: 8000,
+ Kinds: []string{},
+ },
+ }
+
+ expected := &cluster.ClusterTopology{
+ Members: members,
+ Joined: members,
+ Left: []*cluster.Member{},
+ TopologyHash: msg.TopologyHash,
+ }
+ a.Equal(expected, msg)
+ }
+}
+
+func TestRegisterMultipleMembers(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ a := assert.New(t)
+
+ members := []struct {
+ cluster string
+ host string
+ port int
+ }{
+ {"mycluster2", "127.0.0.1", 8001},
+ {"mycluster2", "127.0.0.1", 8002},
+ {"mycluster2", "127.0.0.1", 8003},
+ }
+
+ p, _ := New()
+ defer p.Shutdown(true)
+ for _, member := range members {
+ addr := fmt.Sprintf("%s:%d", member.host, member.port)
+ _p, _ := New()
+ c := newClusterForTest(member.cluster, addr, _p)
+ err := p.StartMember(c)
+ a.NoError(err)
+ t.Cleanup(func() {
+ _p.Shutdown(true)
+ })
+ }
+
+ entries, _, err := p.client.Health().Service("mycluster2", "", true, nil)
+ a.NoError(err)
+
+ found := false
+ for _, entry := range entries {
+ found = false
+ for _, member := range members {
+ if entry.Service.Port == member.port {
+ found = true
+ }
+ }
+ a.Truef(found, "Member port not found - ExtensionID:%v Address: %v:%v",
+ entry.Service.ID, entry.Service.Address, entry.Service.Port)
+ }
+}
+
+func TestUpdateTTL_DoesNotReregisterAfterShutdown(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ a := assert.New(t)
+
+ p, _ := New()
+ c := newClusterForTest("mycluster5", "127.0.0.1:8001", p)
+
+ shutdownShouldHaveResolved := make(chan bool, 1)
+
+ err := p.StartMember(c)
+ a.NoError(err)
+
+ time.Sleep(time.Second)
+ found, _ := findService(t, p)
+ a.True(found, "service was not registered in consul")
+
+ go func() {
+ // if after 5 seconds `Shutdown` did not resolve, assume that it will not resolve until `blockingUpdateTTL` resolves
+ time.Sleep(5 * time.Second)
+ shutdownShouldHaveResolved <- true
+ }()
+
+ err = p.Shutdown(true)
+ a.NoError(err)
+ shutdownShouldHaveResolved <- true
+
+ // since `UpdateTTL` runs in a separate goroutine we need to wait until it is actually finished before checking the member's clusterstatus
+ p.updateTTLWaitGroup.Wait()
+ found, status := findService(t, p)
+ a.Falsef(found, "service was still registered in consul after shutdown (service status: %s)", status)
+}
+
+func findService(t *testing.T, p *Provider) (found bool, status string) {
+ service := p.cluster.Config.Name
+ port := p.cluster.Config.RemoteConfig.Port
+ entries, _, err := p.client.Health().Service(service, "", false, nil)
+ if err != nil {
+ t.Error(err)
+ }
+
+ for _, entry := range entries {
+ if entry.Service.Port == port {
+ return true, entry.Checks.AggregatedStatus()
+ }
+ }
+ return false, ""
+}
diff --git a/cluster/clusterproviders/consul/log.go b/cluster/clusterproviders/consul/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..5a1e13828877fb13e38d5d83d756d5784459d946
--- /dev/null
+++ b/cluster/clusterproviders/consul/log.go
@@ -0,0 +1,11 @@
+package consul
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DebugLevel, "[CONSUL]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/consul/options.go b/cluster/clusterproviders/consul/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..e5cec33299317ddc21cb0e10a9fcd3cda5c8be0b
--- /dev/null
+++ b/cluster/clusterproviders/consul/options.go
@@ -0,0 +1,17 @@
+package consul
+
+import "time"
+
+type Option func(p *Provider)
+
+func WithTTL(ttl time.Duration) Option {
+ return func(p *Provider) {
+ p.ttl = ttl
+ }
+}
+
+func WithRefreshTTL(refreshTTL time.Duration) Option {
+ return func(p *Provider) {
+ p.refreshTTL = refreshTTL
+ }
+}
diff --git a/cluster/clusterproviders/consul/provider_actor.go b/cluster/clusterproviders/consul/provider_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..900c52bbd8dd39595bf97df70362c0a3cd35f57f
--- /dev/null
+++ b/cluster/clusterproviders/consul/provider_actor.go
@@ -0,0 +1,133 @@
+package consul
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/scheduler"
+ "github.com/hashicorp/consul/api"
+ "github.com/hashicorp/consul/api/watch"
+)
+
+type providerActor struct {
+ *Provider
+ actor.Behavior
+ refreshCanceller scheduler.CancelFunc
+}
+
+type (
+ RegisterService struct{}
+ UpdateTTL struct{}
+ MemberListUpdated struct {
+ members []*cluster.Member
+ index uint64
+ }
+)
+
+func (pa *providerActor) Receive(ctx actor.Context) {
+ pa.Behavior.Receive(ctx)
+}
+
+func newProviderActor(provider *Provider) actor.Actor {
+ pa := &providerActor{
+ Behavior: actor.NewBehavior(),
+ Provider: provider,
+ }
+ pa.Become(pa.init)
+ return pa
+}
+
+func (pa *providerActor) init(ctx actor.Context) {
+ switch ctx.Message().(type) {
+ case *actor.Started:
+ ctx.Send(ctx.Self(), &RegisterService{})
+ case *RegisterService:
+ if err := pa.registerService(); err != nil {
+ plog.Error("Failed to register service to consul, will retry", log.Error(err))
+ ctx.Send(ctx.Self(), &RegisterService{})
+ } else {
+ plog.Info("Registered service to consul")
+ refreshScheduler := scheduler.NewTimerScheduler(ctx)
+ pa.refreshCanceller = refreshScheduler.SendRepeatedly(0, pa.refreshTTL, ctx.Self(), &UpdateTTL{})
+ if err := pa.startWatch(ctx); err == nil {
+ pa.Become(pa.running)
+ }
+ }
+ }
+}
+
+func (pa *providerActor) running(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *UpdateTTL:
+ if err := blockingUpdateTTL(pa.Provider); err != nil {
+ plog.Warn("Failed to update TTL", log.Error(err))
+ }
+ case *MemberListUpdated:
+ pa.cluster.MemberList.UpdateClusterTopology(msg.members)
+ case *actor.Stopping:
+ pa.refreshCanceller()
+ if err := pa.deregisterService(); err != nil {
+ plog.Error("Failed to deregister service from consul", log.Error(err))
+ } else {
+ plog.Info("De-registered service from consul")
+ }
+ }
+}
+
+func (pa *providerActor) startWatch(ctx actor.Context) error {
+ params := make(map[string]interface{})
+ params["type"] = "service"
+ params["service"] = pa.clusterName
+ params["passingonly"] = false
+ plan, err := watch.Parse(params)
+ if err != nil {
+ plog.Error("Failed to parse consul watch definition", log.Error(err))
+ return err
+ }
+ plan.Handler = func(index uint64, result interface{}) {
+ pa.processConsulUpdate(index, result, ctx)
+ }
+
+ go func() {
+ if err = plan.RunWithConfig(pa.consulConfig.Address, pa.consulConfig); err != nil {
+ plog.Error("Failed to start consul watch", log.Error(err))
+ panic(err)
+ }
+ }()
+
+ return nil
+}
+
+func (pa *providerActor) processConsulUpdate(index uint64, result interface{}, ctx actor.Context) {
+ serviceEntries, ok := result.([]*api.ServiceEntry)
+ if !ok {
+ plog.Warn("Didn't get expected data from consul watch")
+ return
+ }
+ var members []*cluster.Member
+ for _, v := range serviceEntries {
+ if len(v.Checks) > 0 && v.Checks.AggregatedStatus() == api.HealthPassing {
+ memberId := v.Service.Meta["id"]
+ if memberId == "" {
+ memberId = fmt.Sprintf("%v@%v:%v", pa.clusterName, v.Service.Address, v.Service.Port)
+ plog.Info("meta['id'] was empty, fixed", log.String("id", memberId))
+ }
+ members = append(members, &cluster.Member{
+ Id: memberId,
+ Host: v.Service.Address,
+ Port: int32(v.Service.Port),
+ Kinds: v.Service.Tags,
+ })
+ }
+ }
+
+ // delay the fist update until there is at least one member
+ if len(members) > 0 {
+ ctx.Send(ctx.Self(), &MemberListUpdated{
+ members: members,
+ index: index,
+ })
+ }
+}
diff --git a/cluster/clusterproviders/etcd/etcd_provider.go b/cluster/clusterproviders/etcd/etcd_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..0e3f7cd6e498016738b761df918311d460ccadbc
--- /dev/null
+++ b/cluster/clusterproviders/etcd/etcd_provider.go
@@ -0,0 +1,415 @@
+package etcd
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ clientv3 "go.etcd.io/etcd/client/v3"
+)
+
+type Provider struct {
+ leaseID clientv3.LeaseID
+ cluster *cluster.Cluster
+ baseKey string
+ clusterName string
+ deregistered bool
+ shutdown bool
+ self *Node
+ members map[string]*Node // all, contains self.
+ clusterError error
+ client *clientv3.Client
+ cancelWatch func()
+ cancelWatchCh chan bool
+ keepAliveTTL time.Duration
+ retryInterval time.Duration
+ revision uint64
+ // deregisterCritical time.Duration
+}
+
+func New() (*Provider, error) {
+ return NewWithConfig("/protoactor", clientv3.Config{
+ Endpoints: []string{"127.0.0.1:2379"},
+ DialTimeout: time.Second * 5,
+ })
+}
+
+func NewWithConfig(baseKey string, cfg clientv3.Config) (*Provider, error) {
+ client, err := clientv3.New(cfg)
+ if err != nil {
+ return nil, err
+ }
+ p := &Provider{
+ client: client,
+ keepAliveTTL: 3 * time.Second,
+ retryInterval: 1 * time.Second,
+ baseKey: baseKey,
+ members: map[string]*Node{},
+ cancelWatchCh: make(chan bool),
+ }
+ return p, nil
+}
+
+func (p *Provider) init(c *cluster.Cluster) error {
+ p.cluster = c
+ addr := p.cluster.ActorSystem.Address()
+ host, port, err := splitHostPort(addr)
+ if err != nil {
+ return err
+ }
+
+ p.cluster = c
+ p.clusterName = p.cluster.Config.Name
+ memberID := p.cluster.ActorSystem.ID
+ knownKinds := c.GetClusterKinds()
+ nodeName := fmt.Sprintf("%v@%v", p.clusterName, memberID)
+ p.self = NewNode(nodeName, host, port, knownKinds)
+ p.self.SetMeta("id", p.getID())
+ return nil
+}
+
+func (p *Provider) StartMember(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+
+ // fetch memberlist
+ nodes, err := p.fetchNodes()
+ if err != nil {
+ return err
+ }
+ // initialize members
+ p.updateNodesWithSelf(nodes)
+ p.publishClusterTopologyEvent()
+ p.startWatching()
+
+ // register self
+ if err := p.registerService(); err != nil {
+ return err
+ }
+ ctx := context.TODO()
+ p.startKeepAlive(ctx)
+ return nil
+}
+
+func (p *Provider) StartClient(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+ nodes, err := p.fetchNodes()
+ if err != nil {
+ return err
+ }
+ // initialize members
+ p.updateNodes(nodes)
+ p.publishClusterTopologyEvent()
+ p.startWatching()
+ return nil
+}
+
+func (p *Provider) Shutdown(graceful bool) error {
+ p.shutdown = true
+ if !p.deregistered {
+ err := p.deregisterService()
+ if err != nil {
+ plog.Error("deregisterMember", log.Error(err))
+ return err
+ }
+ p.deregistered = true
+ }
+ if p.cancelWatch != nil {
+ p.cancelWatch()
+ p.cancelWatch = nil
+ }
+ return nil
+}
+
+func (p *Provider) keepAliveForever(ctx context.Context) error {
+ if p.self == nil {
+ return fmt.Errorf("keepalive must be after initialize")
+ }
+
+ data, err := p.self.Serialize()
+ if err != nil {
+ return err
+ }
+ fullKey := p.getEtcdKey()
+
+ var leaseId clientv3.LeaseID
+ leaseId, err = p.newLeaseID()
+ if err != nil {
+ return err
+ }
+ p.setLeaseID(leaseId)
+
+ if leaseId <= 0 {
+ return fmt.Errorf("grant lease failed. leaseId=%d", leaseId)
+ }
+ _, err = p.client.Put(context.TODO(), fullKey, string(data), clientv3.WithLease(leaseId))
+ if err != nil {
+ return err
+ }
+ kaRespCh, err := p.client.KeepAlive(context.TODO(), leaseId)
+ if err != nil {
+ return err
+ }
+
+ for resp := range kaRespCh {
+ if resp == nil {
+ return fmt.Errorf("keep alive failed. resp=%s", resp.String())
+ }
+ // plog.Infof("keep alive %s ttl=%d", p.getID(), resp.TTL)
+ if p.shutdown {
+ return nil
+ }
+ }
+ return nil
+}
+
+func (p *Provider) startKeepAlive(ctx context.Context) {
+ go func() {
+ for !p.shutdown {
+ if err := ctx.Err(); err != nil {
+ plog.Info("Keepalive was stopped.", log.Error(err))
+ return
+ }
+
+ if err := p.keepAliveForever(ctx); err != nil {
+ plog.Info("Failure refreshing service TTL. ReTrying...", log.Duration("after", p.retryInterval), log.Error(err))
+ }
+ time.Sleep(p.retryInterval)
+ }
+ }()
+}
+
+func (p *Provider) getID() string {
+ return p.self.ID
+}
+
+func (p *Provider) getEtcdKey() string {
+ return p.buildKey(p.clusterName, p.getID())
+}
+
+func (p *Provider) registerService() error {
+ data, err := p.self.Serialize()
+ if err != nil {
+ return err
+ }
+ fullKey := p.getEtcdKey()
+ if err != nil {
+ return err
+ }
+ leaseId := p.getLeaseID()
+ if leaseId <= 0 {
+ _leaseId, err := p.newLeaseID()
+ if err != nil {
+ return err
+ }
+ leaseId = _leaseId
+ p.setLeaseID(leaseId)
+ }
+ _, err = p.client.Put(context.TODO(), fullKey, string(data), clientv3.WithLease(leaseId))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (p *Provider) deregisterService() error {
+ fullKey := p.getEtcdKey()
+ _, err := p.client.Delete(context.TODO(), fullKey)
+ return err
+}
+
+func (p *Provider) handleWatchResponse(resp clientv3.WatchResponse) map[string]*Node {
+ changes := map[string]*Node{}
+ for _, ev := range resp.Events {
+ key := string(ev.Kv.Key)
+ nodeId, err := getNodeID(key, "/")
+ if err != nil {
+ plog.Error("Invalid member.", log.String("key", key))
+ continue
+ }
+
+ switch ev.Type {
+ case clientv3.EventTypePut:
+ node, err := NewNodeFromBytes(ev.Kv.Value)
+ if err != nil {
+ plog.Error("Invalid member.", log.String("key", key))
+ continue
+ }
+ if p.self.Equal(node) {
+ plog.Debug("Skip self.", log.String("key", key))
+ continue
+ }
+ if _, ok := p.members[nodeId]; ok {
+ plog.Debug("Update member.", log.String("key", key))
+ } else {
+ plog.Debug("New member.", log.String("key", key))
+ }
+ changes[nodeId] = node
+ case clientv3.EventTypeDelete:
+ node, ok := p.members[nodeId]
+ if !ok {
+ continue
+ }
+ plog.Debug("Delete member.", log.String("key", key))
+ cloned := *node
+ cloned.SetAlive(false)
+ changes[nodeId] = &cloned
+ default:
+ plog.Error("Invalid etcd event.type.", log.String("key", key),
+ log.String("type", ev.Type.String()))
+ }
+ }
+ p.revision = uint64(resp.Header.GetRevision())
+ return changes
+}
+
+func (p *Provider) keepWatching(ctx context.Context) error {
+ clusterKey := p.buildKey(p.clusterName)
+ stream := p.client.Watch(ctx, clusterKey, clientv3.WithPrefix())
+ return p._keepWatching(stream)
+}
+
+func (p *Provider) _keepWatching(stream clientv3.WatchChan) error {
+ for resp := range stream {
+ if err := resp.Err(); err != nil {
+ plog.Error("Failure watching service.")
+ return err
+ }
+ if len(resp.Events) <= 0 {
+ plog.Error("Empty etcd.events.", log.Int("events", len(resp.Events)))
+ continue
+ }
+ nodesChanges := p.handleWatchResponse(resp)
+ p.updateNodesWithChanges(nodesChanges)
+ p.publishClusterTopologyEvent()
+ }
+ return nil
+}
+
+func (p *Provider) startWatching() {
+ ctx := context.TODO()
+ ctx, cancel := context.WithCancel(ctx)
+ p.cancelWatch = cancel
+ go func() {
+ for !p.shutdown {
+ if err := p.keepWatching(ctx); err != nil {
+ plog.Error("Failed to keepWatching.", log.Error(err))
+ p.clusterError = err
+ }
+ }
+ }()
+}
+
+// GetHealthStatus returns an error if the cluster health status has problems
+func (p *Provider) GetHealthStatus() error {
+ return p.clusterError
+}
+
+func newContext(timeout time.Duration) (context.Context, context.CancelFunc) {
+ return context.WithTimeout(context.TODO(), timeout)
+}
+
+func (p *Provider) buildKey(names ...string) string {
+ return strings.Join(append([]string{p.baseKey}, names...), "/")
+}
+
+func (p *Provider) fetchNodes() ([]*Node, error) {
+ key := p.buildKey(p.clusterName)
+ resp, err := p.client.Get(context.TODO(), key, clientv3.WithPrefix())
+ if err != nil {
+ return nil, err
+ }
+ var nodes []*Node
+ for _, v := range resp.Kvs {
+ n := Node{}
+ if err := n.Deserialize(v.Value); err != nil {
+ return nil, err
+ }
+ nodes = append(nodes, &n)
+ }
+ p.revision = uint64(resp.Header.GetRevision())
+ // plog.Debug("fetch nodes",
+ // log.Uint64("raft term", resp.Header.GetRaftTerm()),
+ // log.Int64("revision", resp.Header.GetRevision()))
+ return nodes, nil
+}
+
+func (p *Provider) updateNodes(members []*Node) {
+ for _, n := range members {
+ p.members[n.ID] = n
+ }
+}
+
+func (p *Provider) updateNodesWithSelf(members []*Node) {
+ p.updateNodes(members)
+ p.members[p.self.ID] = p.self
+}
+
+func (p *Provider) updateNodesWithChanges(changes map[string]*Node) {
+ for memberId, member := range changes {
+ p.members[memberId] = member
+ if !member.IsAlive() {
+ delete(p.members, memberId)
+ }
+ }
+}
+
+func (p *Provider) createClusterTopologyEvent() []*cluster.Member {
+ res := make([]*cluster.Member, len(p.members))
+ i := 0
+ for _, m := range p.members {
+ res[i] = m.MemberStatus()
+ i++
+ }
+ return res
+}
+
+func (p *Provider) publishClusterTopologyEvent() {
+ res := p.createClusterTopologyEvent()
+ plog.Info("Update cluster.", log.Int("members", len(res)))
+ // for _, m := range res {
+ // plog.Info("\t", log.Object("member", m))
+ // }
+ p.cluster.MemberList.UpdateClusterTopology(res)
+ // p.cluster.ActorSystem.EventStream.Publish(res)
+}
+
+func (p *Provider) getLeaseID() clientv3.LeaseID {
+ return (clientv3.LeaseID)(atomic.LoadInt64((*int64)(&p.leaseID)))
+}
+
+func (p *Provider) setLeaseID(leaseID clientv3.LeaseID) {
+ atomic.StoreInt64((*int64)(&p.leaseID), (int64)(leaseID))
+}
+
+func (p *Provider) newLeaseID() (clientv3.LeaseID, error) {
+ ttlSecs := int64(p.keepAliveTTL / time.Second)
+ resp, err := p.client.Grant(context.TODO(), ttlSecs)
+ if err != nil {
+ return 0, err
+ }
+ return resp.ID, nil
+}
+
+func splitHostPort(addr string) (host string, port int, err error) {
+ if h, p, e := net.SplitHostPort(addr); e != nil {
+ if addr != "nonhost" {
+ err = e
+ }
+ host = "nonhost"
+ port = -1
+ } else {
+ host = h
+ port, err = strconv.Atoi(p)
+ }
+ return
+}
diff --git a/cluster/clusterproviders/etcd/etcd_provider_test.go b/cluster/clusterproviders/etcd/etcd_provider_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2eda4a59cb35178bccf10c4563169b8ddf5a8c1
--- /dev/null
+++ b/cluster/clusterproviders/etcd/etcd_provider_test.go
@@ -0,0 +1,186 @@
+package etcd
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/assert"
+)
+
+func newClusterForTest(name string, addr string, cp cluster.ClusterProvider) *cluster.Cluster {
+ host, _port, err := net.SplitHostPort(addr)
+ if err != nil {
+ panic(err)
+ }
+ port, _ := strconv.Atoi(_port)
+ remoteConfig := remote.Configure(host, port)
+ config := cluster.Configure(name, cp, nil, remoteConfig)
+
+ system := actor.NewActorSystem()
+ c := cluster.New(system, config)
+ // use for test without start remote
+ c.ActorSystem.ProcessRegistry.Address = addr
+ c.MemberList = cluster.NewMemberList(c)
+ c.Remote = remote.NewRemote(c.ActorSystem, c.Config.RemoteConfig)
+
+ return c
+}
+
+func TestStartMember(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ a := assert.New(t)
+
+ p, err := New()
+ a.NoError(err)
+ defer p.Shutdown(true)
+
+ c := newClusterForTest("test_etcd_provider", "127.0.0.1:8000", p)
+ eventstream := c.ActorSystem.EventStream
+ ch := make(chan interface{}, 16)
+
+ eventstream.Subscribe(func(m interface{}) {
+ if _, ok := m.(*cluster.ClusterTopology); ok {
+ ch <- m
+ }
+ })
+
+ err = p.StartMember(c)
+ a.NoError(err)
+
+ select {
+ case <-time.After(5 * time.Second):
+ a.FailNow("no member joined yet")
+
+ case m := <-ch:
+ // member joined
+ msg, _ := m.(*cluster.ClusterTopology)
+
+ members := []*cluster.Member{
+ {
+ // Id: "test_etcd_provider@127.0.0.1:8000",
+ Id: fmt.Sprintf("test_etcd_provider@%s", c.ActorSystem.ID),
+ Host: "127.0.0.1",
+ Port: 8000,
+ Kinds: []string{},
+ },
+ }
+
+ expected := &cluster.ClusterTopology{
+ Members: members,
+ Joined: members,
+ Left: []*cluster.Member{},
+ TopologyHash: msg.TopologyHash,
+ }
+ a.Equal(expected, msg)
+
+ }
+}
+
+func TestStartMember_Multiple(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ a := assert.New(t)
+ members := []struct {
+ cluster string
+ host string
+ port int
+ }{
+ {"mycluster2", "127.0.0.1", 8001},
+ {"mycluster2", "127.0.0.1", 8002},
+ {"mycluster2", "127.0.0.1", 8003},
+ }
+
+ p := make([]*Provider, len(members))
+
+ var err error
+
+ t.Cleanup(func() {
+ for i := range p {
+ _ = p[i].Shutdown(true)
+ }
+ })
+
+ for i, member := range members {
+ addr := fmt.Sprintf("%s:%d", member.host, member.port)
+ p[i], err = New()
+ a.NoError(err)
+
+ c := newClusterForTest(member.cluster, addr, p[i])
+ err := p[i].StartMember(c)
+ a.NoError(err)
+ }
+
+ isNodesEqual := func(nodes []*Node) bool {
+ for _, node := range nodes {
+ for _, member := range members {
+ if node.Host == member.host && node.Port == member.port {
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
+ for i := range p {
+ nodes, err := p[i].fetchNodes()
+ a.NoError(err)
+ a.Equal(len(members), len(nodes))
+ flag := isNodesEqual(nodes)
+ a.Truef(flag, "Member not found - %+v", p[i].self)
+ }
+}
+
+//func TestUpdateMemberState(t *testing.T) {
+// if testing.Short() {
+// return
+// }
+// assert := assert.New(t)
+//
+// p, _ := New()
+// defer p.Shutdown(true)
+//
+// c := newClusterForTest("mycluster3", "127.0.0.1:8000", p)
+// err := p.StartMember(c)
+// assert.NoError(err)
+//
+// state := cluster.ClusterState{[]string{"yes"}}
+// err = p.UpdateClusterState(state)
+// assert.NoError(err)
+//}
+//
+//func TestUpdateMemberState_DoesNotReregisterAfterShutdown(t *testing.T) {
+// if testing.Short() {
+// return
+// }
+// assert := assert.New(t)
+//
+// p, _ := New()
+// c := newClusterForTest("mycluster4", "127.0.0.1:8001", p)
+// err := p.StartMember(c)
+// assert.NoError(err)
+// t.Cleanup(func() {
+// p.Shutdown(true)
+// })
+//
+// state := cluster.ClusterState{[]string{"yes"}}
+// err = p.UpdateClusterState(state)
+// assert.NoError(err)
+//
+// err = p.Shutdown(true)
+// assert.NoError(err)
+//
+// err = p.UpdateClusterState(state)
+// assert.Error(err)
+//}
diff --git a/cluster/clusterproviders/etcd/log.go b/cluster/clusterproviders/etcd/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..45da2460921b3441a9df7cac4f625740e98df2cb
--- /dev/null
+++ b/cluster/clusterproviders/etcd/log.go
@@ -0,0 +1,11 @@
+package etcd
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DefaultLevel, "[CLUSTER] [ETCD]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/etcd/node.go b/cluster/clusterproviders/etcd/node.go
new file mode 100644
index 0000000000000000000000000000000000000000..da278ba03e07180eb32eb26e1e15fcb3261e6ca7
--- /dev/null
+++ b/cluster/clusterproviders/etcd/node.go
@@ -0,0 +1,107 @@
+package etcd
+
+import (
+ "encoding/json"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+type Node struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Host string `json:"host"`
+ Address string `json:"address"`
+ Port int `json:"port"`
+ Kinds []string `json:"kinds"`
+ Meta map[string]string `json:"-"`
+ Alive bool `json:"alive"`
+}
+
+func NewNode(name, host string, port int, kinds []string) *Node {
+ return &Node{
+ ID: name,
+ Name: name,
+ Address: host,
+ Host: host,
+ Port: port,
+ Kinds: kinds,
+ Meta: map[string]string{},
+ Alive: true,
+ }
+}
+
+func NewNodeFromBytes(data []byte) (*Node, error) {
+ n := Node{}
+ if err := json.Unmarshal(data, &n); err != nil {
+ return nil, err
+ }
+ return &n, nil
+}
+
+func (n *Node) GetAddress() (host string, port int) {
+ host = n.Host
+ port = n.Port
+ if host == "" {
+ host = n.Address
+ }
+ return
+}
+
+func (n *Node) Equal(other *Node) bool {
+ if n == nil || other == nil {
+ return false
+ }
+ if n == other {
+ return true
+ }
+ return n.ID == other.ID
+}
+
+func (n *Node) GetMeta(name string) (string, bool) {
+ if n.Meta == nil {
+ return "", false
+ }
+ val, ok := n.Meta[name]
+ return val, ok
+}
+
+func (n *Node) MemberStatus() *cluster.Member {
+ host, port := n.GetAddress()
+ kinds := n.Kinds
+ if kinds == nil {
+ kinds = []string{}
+ }
+ return &cluster.Member{
+ Id: n.ID,
+ Host: host,
+ Port: int32(port),
+ Kinds: kinds,
+ }
+}
+
+func (n *Node) SetMeta(name string, val string) {
+ if n.Meta == nil {
+ n.Meta = map[string]string{}
+ }
+ n.Meta[name] = val
+}
+
+func (n *Node) Serialize() ([]byte, error) {
+ data, err := json.Marshal(n)
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
+func (n *Node) Deserialize(data []byte) error {
+ return json.Unmarshal(data, n)
+}
+
+func (n *Node) IsAlive() bool {
+ return n.Alive
+}
+
+func (n *Node) SetAlive(alive bool) {
+ n.Alive = alive
+}
diff --git a/cluster/clusterproviders/etcd/utils.go b/cluster/clusterproviders/etcd/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b8b14d2727866e5fd5d68a7d8e18b48786ccbf6
--- /dev/null
+++ b/cluster/clusterproviders/etcd/utils.go
@@ -0,0 +1,15 @@
+package etcd
+
+import (
+ "fmt"
+ "strings"
+)
+
+func getNodeID(key string, sep string) (string, error) {
+ tmpArr := strings.Split(key, sep)
+ if len(tmpArr) == 0 {
+ return "", fmt.Errorf("invalid key or sep")
+ }
+ lastIndex := len(tmpArr) - 1
+ return tmpArr[lastIndex], nil
+}
diff --git a/cluster/clusterproviders/k8s/k8s_cluster_monitor.go b/cluster/clusterproviders/k8s/k8s_cluster_monitor.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ed0cca802a555c0edb5fefc96af928944d9317c
--- /dev/null
+++ b/cluster/clusterproviders/k8s/k8s_cluster_monitor.go
@@ -0,0 +1,78 @@
+package k8s
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/scheduler"
+)
+
+type k8sClusterMonitorActor struct {
+ *Provider
+ actor.Behavior
+
+ refreshCanceller scheduler.CancelFunc
+}
+
+func (kcm *k8sClusterMonitorActor) Receive(ctx actor.Context) { kcm.Behavior.Receive(ctx) }
+
+func (kcm *k8sClusterMonitorActor) init(ctx actor.Context) {
+ switch r := ctx.Message().(type) {
+ case *RegisterMember:
+ // make sure timeout is set to some meaningful value
+ timeout := getTimeout(ctx, kcm)
+
+ if err := kcm.registerMember(timeout); err != nil {
+ plog.Error("Failed to register service to k8s, will retry", log.Error(err))
+ ctx.Send(ctx.Self(), r)
+ return
+ }
+ plog.Info("Registered service to k8s")
+ case *DeregisterMember:
+ plog.Debug("Deregistering service from k8s")
+ timeout := getTimeout(ctx, kcm)
+
+ if err := kcm.deregisterMember(timeout); err != nil {
+ plog.Error("Failed to deregister service from k8s, proceeding with shutdown", log.Error(err))
+ } else {
+ plog.Info("Deregistered service from k8s")
+ }
+ ctx.Respond(&DeregisterMemberResponse{})
+ case *StartWatchingCluster:
+ if err := kcm.startWatchingCluster(); err != nil {
+ plog.Error("Failed to start watching k8s cluster, will retry", log.Error(err))
+ ctx.Send(ctx.Self(), r)
+ return
+ }
+ plog.Info("k8s cluster started to being watched")
+ case *StopWatchingCluster:
+ if kcm.cancelWatch != nil {
+ kcm.cancelWatch()
+ }
+ ctx.Respond(&StopWatchingClusterResponse{})
+ }
+}
+
+func getTimeout(ctx actor.Context, kcm *k8sClusterMonitorActor) time.Duration {
+ timeout := ctx.ReceiveTimeout()
+ if timeout.Microseconds() == 0 {
+ timeout = kcm.Provider.cluster.Config.RequestTimeoutTime
+ if timeout.Microseconds() == 0 {
+ timeout = time.Second * 5 // default to 5 seconds
+ }
+ }
+
+ return timeout
+}
+
+// creates and initializes a new k8sClusterMonitorActor in the heap and
+// returns a reference to its memory address
+func newClusterMonitor(provider *Provider) actor.Actor {
+ kcm := k8sClusterMonitorActor{
+ Behavior: actor.NewBehavior(),
+ Provider: provider,
+ }
+ kcm.Become(kcm.init)
+ return &kcm
+}
diff --git a/cluster/clusterproviders/k8s/k8s_provider.go b/cluster/clusterproviders/k8s/k8s_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..7cb6f6f4284a7edb8ffd9e4aa793c387118a3c0d
--- /dev/null
+++ b/cluster/clusterproviders/k8s/k8s_provider.go
@@ -0,0 +1,429 @@
+package k8s
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/google/uuid"
+ v1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+)
+
+var ProviderShuttingDownError = fmt.Errorf("kubernetes cluster provider is being shut down")
+
+// Convenience type to store cluster labels
+type Labels map[string]string
+
+// This data structure provides of k8s as cluster provider for Proto.Actor
+type Provider struct {
+ id string
+ cluster *cluster.Cluster
+ clusterName string
+ podName string
+ host string
+ address string
+ namespace string
+ knownKinds []string
+ clusterPods map[types.UID]*v1.Pod
+ port int
+ client *kubernetes.Clientset
+ clusterMonitor *actor.PID
+ deregistered bool
+ shutdown bool
+ cancelWatch context.CancelFunc
+}
+
+// make sure our Provider complies with the ClusterProvider interface
+var _ cluster.ClusterProvider = (*Provider)(nil)
+
+// New crates a new k8s Provider in the heap and return back a reference to its memory address
+func New(opts ...Option) (*Provider, error) {
+ // create new default k8s config
+ config, err := rest.InClusterConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ return NewWithConfig(config, opts...)
+}
+
+// NewWithConfig creates a new k8s Provider in the heap using the given configuration
+// and options, it returns a reference to its memory address or an error
+func NewWithConfig(config *rest.Config, opts ...Option) (*Provider, error) {
+ clientset, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ return nil, err
+ }
+
+ p := Provider{
+ client: clientset,
+ }
+
+ // process given options
+ for _, opt := range opts {
+ opt(&p)
+ }
+ return &p, nil
+}
+
+// initializes the cluster provider
+func (p *Provider) init(c *cluster.Cluster) error {
+ host, port, err := c.ActorSystem.GetHostPort()
+ if err != nil {
+ return err
+ }
+
+ p.cluster = c
+ p.id = strings.Replace(uuid.New().String(), "-", "", -1)
+ p.knownKinds = c.GetClusterKinds()
+ p.clusterName = c.Config.Name
+ p.clusterPods = make(map[types.UID]*v1.Pod)
+ p.host = host
+ p.port = port
+ p.address = fmt.Sprintf("%s:%d", host, port)
+ return nil
+}
+
+// StartMember registers the member in the cluster and start it
+func (p *Provider) StartMember(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+
+ if err := p.startClusterMonitor(c); err != nil {
+ return err
+ }
+
+ p.registerMemberAsync(c)
+ p.startWatchingClusterAsync(c)
+
+ return nil
+}
+
+// StartClient starts the k8s client and monitor watch
+func (p *Provider) StartClient(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+
+ if err := p.startClusterMonitor(c); err != nil {
+ return err
+ }
+
+ p.startWatchingClusterAsync(c)
+ return nil
+}
+
+func (p *Provider) Shutdown(graceful bool) error {
+ if p.shutdown {
+ // we are already shut down or shutting down
+ return nil
+ }
+
+ p.shutdown = true
+
+ plog.Info("Shutting down k8s cluster provider")
+ if p.clusterMonitor != nil {
+ if err := p.cluster.ActorSystem.Root.RequestFuture(p.clusterMonitor, &DeregisterMember{}, 5*time.Second).Wait(); err != nil {
+ plog.Error("Failed to deregister member - cluster monitor did not respond, proceeding with shutdown", log.Error(err))
+ }
+
+ if err := p.cluster.ActorSystem.Root.RequestFuture(p.clusterMonitor, &StopWatchingCluster{}, 5*time.Second).Wait(); err != nil {
+ plog.Error("Failed to deregister member - cluster monitor did not respond, proceeding with shutdown", log.Error(err))
+ }
+
+ _ = p.cluster.ActorSystem.Root.StopFuture(p.clusterMonitor).Wait()
+ p.clusterMonitor = nil
+ }
+
+ return nil
+}
+
+// starts the cluster monitor in its own goroutine
+func (p *Provider) startClusterMonitor(c *cluster.Cluster) error {
+ var err error
+ p.clusterMonitor, err = c.ActorSystem.Root.SpawnNamed(actor.PropsFromProducer(func() actor.Actor {
+ return newClusterMonitor(p)
+ }), "k8s-cluster-monitor")
+
+ if err != nil {
+ plog.Error("Failed to start k8s-cluster-monitor actor", log.Error(err))
+ return err
+ }
+
+ p.podName, _ = os.Hostname()
+ return nil
+}
+
+// registers itself as a member asynchronously using an actor
+func (p *Provider) registerMemberAsync(c *cluster.Cluster) {
+ msg := RegisterMember{}
+ c.ActorSystem.Root.Send(p.clusterMonitor, &msg)
+}
+
+// registers itself as a member in k8s cluster
+func (p *Provider) registerMember(timeout time.Duration) error {
+ plog.Info(fmt.Sprintf("Registering service %s on %s", p.podName, p.address))
+
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+
+ pod, err := p.client.CoreV1().Pods(p.retrieveNamespace()).Get(ctx, p.podName, metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("unable to get own pod information for %s: %w", p.podName, err)
+ }
+
+ plog.Info(fmt.Sprintf("Using Kubernetes namespace: %s\nUsing Kubernetes port: %d", pod.Namespace, p.port))
+
+ labels := Labels{
+ LabelCluster: p.clusterName,
+ LabelPort: fmt.Sprintf("%d", p.port),
+ LabelMemberID: p.id,
+ }
+
+ // add known kinds to labels
+ for _, kind := range p.knownKinds {
+ labelkey := fmt.Sprintf("%s-%s", LabelKind, kind)
+ labels[labelkey] = "true"
+ }
+
+ // add existing labels back
+ for key, value := range pod.ObjectMeta.Labels {
+ labels[key] = value
+ }
+ pod.SetLabels(labels)
+
+ return p.replacePodLabels(ctx, pod)
+}
+
+func (p *Provider) startWatchingClusterAsync(c *cluster.Cluster) {
+ msg := StartWatchingCluster{p.clusterName}
+ c.ActorSystem.Root.Send(p.clusterMonitor, &msg)
+}
+
+func (p *Provider) startWatchingCluster() error {
+ selector := fmt.Sprintf("%s=%s", LabelCluster, p.clusterName)
+
+ plog.Debug(fmt.Sprintf("Starting to watch pods with %s", selector), log.String("selector", selector))
+
+ ctx, cancel := context.WithCancel(context.Background())
+ p.cancelWatch = cancel
+
+ // start a new goroutine to monitor the cluster events
+ go func() {
+ for {
+ select {
+ case <-ctx.Done():
+ plog.Debug("Stopping watch on pods")
+ return
+ default:
+ if err := p.watchPods(ctx, selector); err != nil {
+ plog.Error("Error watching pods, will retry", log.Error(err))
+ time.Sleep(5 * time.Second)
+ }
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (p *Provider) watchPods(ctx context.Context, selector string) error {
+ watcher, err := p.client.CoreV1().Pods(p.retrieveNamespace()).Watch(context.Background(), metav1.ListOptions{LabelSelector: selector, Watch: true})
+ if err != nil {
+ err = fmt.Errorf("unable to watch pods: %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ return err
+ }
+
+ plog.Info("Pod watcher started")
+
+ for {
+ select {
+ case <-ctx.Done():
+ watcher.Stop()
+ return nil
+ case event, ok := <-watcher.ResultChan():
+ if !ok {
+ return fmt.Errorf("pod watcher channel closed abruptly")
+ }
+ pod, ok := event.Object.(*v1.Pod)
+ if !ok {
+ err := fmt.Errorf("could not cast %#v[%T] into v1.Pod", event.Object, event.Object)
+ plog.Error(err.Error(), log.Error(err))
+ continue
+ }
+
+ p.processPodEvent(event, pod)
+ }
+ }
+}
+
+func (p *Provider) processPodEvent(event watch.Event, pod *v1.Pod) {
+ plog.Debug("Watcher reported event for pod", log.Object("eventType", event.Type), log.String("podName", pod.ObjectMeta.Name))
+
+ podClusterName, hasClusterName := pod.ObjectMeta.Labels[LabelCluster]
+ if !hasClusterName {
+ plog.Info("The pod is not a cluster member", log.Object("podName", pod.ObjectMeta.Name))
+ delete(p.clusterPods, pod.UID) // pod could have been in the cluster, but then it was deregistered
+ } else if podClusterName != p.clusterName {
+ plog.Info("The pod is a member of another cluster", log.Object("podName", pod.ObjectMeta.Name), log.String("otherCluster", podClusterName))
+ return
+ } else {
+ switch event.Type {
+ case watch.Deleted:
+ delete(p.clusterPods, pod.UID)
+ case watch.Error:
+ err := apierrors.FromObject(event.Object)
+ plog.Error(err.Error(), log.Error(err))
+ default:
+ p.clusterPods[pod.UID] = pod
+ }
+ }
+
+ if plog.Level() == log.DebugLevel {
+ logCurrentPods(p.clusterPods)
+ }
+
+ members := mapPodsToMembers(p.clusterPods)
+
+ plog.Info("Topology received from Kubernetes", log.Object("members", members))
+ p.cluster.MemberList.UpdateClusterTopology(members)
+}
+
+func logCurrentPods(clusterPods map[types.UID]*v1.Pod) {
+ podNames := make([]string, 0, len(clusterPods))
+ for _, pod := range clusterPods {
+ podNames = append(podNames, pod.ObjectMeta.Name)
+ }
+ plog.Debug("Detected cluster pods are now", log.Int("numberOfPods", len(clusterPods)), log.Object("podNames", podNames))
+}
+
+func mapPodsToMembers(clusterPods map[types.UID]*v1.Pod) []*cluster.Member {
+ members := make([]*cluster.Member, 0, len(clusterPods))
+ for _, clusterPod := range clusterPods {
+ if clusterPod.Status.Phase == "Running" && len(clusterPod.Status.PodIPs) > 0 {
+
+ var kinds []string
+ for key, value := range clusterPod.ObjectMeta.Labels {
+ if strings.HasPrefix(key, LabelKind) && value == "true" {
+ kinds = append(kinds, strings.Replace(key, fmt.Sprintf("%s-", LabelKind), "", 1))
+ }
+ }
+
+ host := clusterPod.Status.PodIP
+ port, err := strconv.Atoi(clusterPod.ObjectMeta.Labels[LabelPort])
+ if err != nil {
+ err = fmt.Errorf("can not convert pod meta %s into integer: %w", LabelPort, err)
+ plog.Error(err.Error(), log.Error(err))
+ continue
+ }
+
+ mid := clusterPod.ObjectMeta.Labels[LabelMemberID]
+ alive := true
+ for _, status := range clusterPod.Status.ContainerStatuses {
+ if !status.Ready {
+ plog.Debug("Pod container is not ready", log.String("podName", clusterPod.ObjectMeta.Name), log.String("containerName", status.Name))
+ alive = false
+ break
+ }
+ }
+
+ if !alive {
+ continue
+ }
+
+ plog.Debug("Pod is running and all containers are ready", log.String("podName", clusterPod.ObjectMeta.Name), log.Object("podIPs", clusterPod.Status.PodIPs), log.String("podPhase", string(clusterPod.Status.Phase)))
+
+ members = append(members, &cluster.Member{
+ Id: mid,
+ Host: host,
+ Port: int32(port),
+ Kinds: kinds,
+ })
+ } else {
+ plog.Debug("Pod is not in Running state", log.String("podName", clusterPod.ObjectMeta.Name), log.Object("podIPs", clusterPod.Status.PodIPs), log.String("podPhase", string(clusterPod.Status.Phase)))
+ }
+ }
+
+ return members
+}
+
+// deregister itself as a member from a k8s cluster
+func (p *Provider) deregisterMember(timeout time.Duration) error {
+ plog.Info(fmt.Sprintf("Deregistering service %s from %s", p.podName, p.address))
+
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+
+ pod, err := p.client.CoreV1().Pods(p.retrieveNamespace()).Get(ctx, p.podName, metav1.GetOptions{})
+ if err != nil {
+ return fmt.Errorf("unable to get own pod information for %s: %w", p.podName, err)
+ }
+
+ labels := pod.GetLabels()
+
+ for labelKey := range labels {
+ if strings.HasPrefix(labelKey, LabelPrefix) {
+ delete(labels, labelKey)
+ }
+ }
+
+ pod.SetLabels(labels)
+
+ return p.replacePodLabels(ctx, pod)
+}
+
+// prepares a patching payload and sends it to kubernetes to replace labels
+func (p *Provider) replacePodLabels(ctx context.Context, pod *v1.Pod) error {
+ plog.Debug("Setting pod labels to ", log.Object("labels", pod.GetLabels()))
+
+ payload := []struct {
+ Op string `json:"op"`
+ Path string `json:"path"`
+ Value Labels `json:"value"`
+ }{
+ {
+ Op: "replace",
+ Path: "/metadata/labels",
+ Value: pod.GetLabels(),
+ },
+ }
+
+ payloadData, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("unable to update pod labels, operation failed: %w", err)
+ }
+
+ _, patcherr := p.client.CoreV1().Pods(pod.GetNamespace()).Patch(ctx, pod.GetName(), types.JSONPatchType, payloadData, metav1.PatchOptions{})
+ return patcherr
+}
+
+// get the namespace of the current pod
+func (p *Provider) retrieveNamespace() string {
+ if (p.namespace) == "" {
+ filename := filepath.Join(string(filepath.Separator), "var", "run", "secrets", "kubernetes.io", "serviceaccount", "namespace")
+ content, err := os.ReadFile(filename)
+ if err != nil {
+ plog.Warn(fmt.Sprintf("Could not read %s contents defaulting to empty namespace: %s", filename, err.Error()))
+ return p.namespace
+ }
+ p.namespace = string(content)
+ }
+
+ return p.namespace
+}
diff --git a/cluster/clusterproviders/k8s/k8s_provider_test.go b/cluster/clusterproviders/k8s/k8s_provider_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..226a34a0ff18a654f5b50002d42c35431c2331ec
--- /dev/null
+++ b/cluster/clusterproviders/k8s/k8s_provider_test.go
@@ -0,0 +1,174 @@
+package k8s
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "os"
+ "strconv"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/assert"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func newClusterForTest(name string, addr string, cp cluster.ClusterProvider, id cluster.IdentityLookup) *cluster.Cluster {
+ host, _port, err := net.SplitHostPort(addr)
+ if err != nil {
+ panic(err)
+ }
+ port, _ := strconv.Atoi(_port)
+ remoteConfig := remote.Configure(host, port)
+ config := cluster.Configure(name, cp, id, remoteConfig)
+
+ system := actor.NewActorSystem()
+ c := cluster.New(system, config)
+
+ // use for test without start remote
+ c.ActorSystem.ProcessRegistry.Address = addr
+ c.MemberList = cluster.NewMemberList(c)
+ c.Remote = remote.NewRemote(system, config.RemoteConfig)
+ return c
+}
+
+func TestStartMember(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
+ t.Skipf("Skipped k8s testcases")
+ }
+ assert := assert.New(t)
+
+ p, _ := New()
+ p, newErr := New()
+ if newErr != nil {
+ panic(fmt.Errorf("could not create new cluster provider: %w", newErr))
+ }
+ id := disthash.New()
+ defer p.Shutdown(true)
+
+ c := newClusterForTest("k8scluster", "127.0.0.1:8000", p, id)
+ eventstream := c.ActorSystem.EventStream
+ ch := make(chan interface{}, 16)
+ eventstream.Subscribe(func(m interface{}) {
+ if _, ok := m.(*cluster.ClusterTopology); ok {
+ ch <- m
+ }
+ })
+
+ err := p.StartMember(c)
+ assert.NoError(err)
+
+ select {
+ case <-time.After(10 * time.Second):
+ assert.FailNow("no member joined yet")
+
+ case m := <-ch:
+ msg := m.(*cluster.ClusterTopology)
+ // member joined
+ members := []*cluster.Member{
+ {
+ Id: "k8scluster@127.0.0.1:8000",
+ Host: "127.0.0.1",
+ Port: 8000,
+ Kinds: []string{},
+ },
+ }
+
+ expected := &cluster.ClusterTopology{
+ Members: members,
+ Joined: members,
+ }
+ assert.Equal(expected, msg)
+ }
+}
+
+func TestRegisterMultipleMembers(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
+ t.Skipf("Skipped k8s testcases")
+ }
+ assert := assert.New(t)
+
+ members := []struct {
+ cluster string
+ host string
+ port int
+ }{
+ {"k8scluster2", "127.0.0.1", 8001},
+ {"k8scluster2", "127.0.0.1", 8002},
+ {"k8scluster2", "127.0.0.1", 8003},
+ }
+
+ p, _ := New()
+ defer p.Shutdown(true)
+ for _, member := range members {
+ addr := fmt.Sprintf("%s:%d", member.host, member.port)
+ _p, _ := New()
+ _id := disthash.New()
+ c := newClusterForTest(member.cluster, addr, _p, _id)
+ err := p.StartMember(c)
+ assert.NoError(err)
+ t.Cleanup(func() {
+ _p.Shutdown(true)
+ })
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
+ pods, err := p.client.CoreV1().Pods(p.retrieveNamespace()).List(ctx, metav1.ListOptions{})
+ assert.NoError(err)
+ assert.Equal(pods.Size(), len(members))
+}
+
+func TestUpdateMemberState(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
+ t.Skipf("Skipped k8s testcases")
+ }
+ assert := assert.New(t)
+
+ p, _ := New()
+ id := disthash.New()
+ defer p.Shutdown(true)
+
+ c := newClusterForTest("k8scluster3", "127.0.0.1:8000", p, id)
+ err := p.StartMember(c)
+ assert.NoError(err)
+}
+
+func TestUpdateMemberState_DoesNotReregisterAfterShutdown(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
+ t.Skipf("Skipped k8s testcases")
+ }
+ assert := assert.New(t)
+
+ p, _ := New()
+ id := disthash.New()
+ c := newClusterForTest("k8scluster4", "127.0.0.1:8001", p, id)
+ err := p.StartMember(c)
+ assert.NoError(err)
+ t.Cleanup(func() {
+ p.Shutdown(true)
+ })
+
+ err = p.Shutdown(true)
+ assert.NoError(err)
+
+ assert.Equal(ProviderShuttingDownError, err)
+}
diff --git a/cluster/clusterproviders/k8s/labels.go b/cluster/clusterproviders/k8s/labels.go
new file mode 100644
index 0000000000000000000000000000000000000000..10a3c341932979e1911a84b4d778cdacb13cf530
--- /dev/null
+++ b/cluster/clusterproviders/k8s/labels.go
@@ -0,0 +1,11 @@
+package k8s
+
+// Label keys that will be used to update the Pods metadata
+const (
+ LabelPrefix = "cluster.proto.actor/"
+ LabelPort = LabelPrefix + "port"
+ LabelKind = LabelPrefix + "kind"
+ LabelCluster = LabelPrefix + "cluster"
+ LabelStatusValue = LabelPrefix + "status-value"
+ LabelMemberID = LabelPrefix + "member-id"
+)
diff --git a/cluster/clusterproviders/k8s/log.go b/cluster/clusterproviders/k8s/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..500e947d0ce2bcb95ec88cef2f1e0a08bda0985c
--- /dev/null
+++ b/cluster/clusterproviders/k8s/log.go
@@ -0,0 +1,11 @@
+package k8s
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DebugLevel, "[CLUSTER] [KUBERNETES]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/k8s/messages.go b/cluster/clusterproviders/k8s/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..95c0bb36a182f7feb0355a3a9cd9e140b5b4c17a
--- /dev/null
+++ b/cluster/clusterproviders/k8s/messages.go
@@ -0,0 +1,21 @@
+package k8s
+
+// RegisterMember message used to register a new member in k8s
+type RegisterMember struct{}
+
+// DeregisterMember Empty struct used to deregister a member from k8s
+type DeregisterMember struct{}
+
+// DeregisterMemberResponse sent back from cluster monitor when deregistering completes or fails
+type DeregisterMemberResponse struct{}
+
+// StartWatchingCluster message used to start watching a k8s cluster
+type StartWatchingCluster struct {
+ ClusterName string
+}
+
+// StopWatchingCluster message used to stop watching a k8s cluster
+type StopWatchingCluster struct{}
+
+// StopWatchingClusterResponse sent back from cluster monitor when stop watching completes
+type StopWatchingClusterResponse struct{}
diff --git a/cluster/clusterproviders/k8s/options.go b/cluster/clusterproviders/k8s/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..c7708009c67aec829070f71dbd44cdf19fd087dd
--- /dev/null
+++ b/cluster/clusterproviders/k8s/options.go
@@ -0,0 +1,4 @@
+package k8s
+
+// Convenience type to refer to Option callables
+type Option func(p *Provider)
diff --git a/cluster/clusterproviders/test/log.go b/cluster/clusterproviders/test/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..daf1626c27f4ab0363802a2165422efe611667aa
--- /dev/null
+++ b/cluster/clusterproviders/test/log.go
@@ -0,0 +1,11 @@
+package test
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DebugLevel, "[TEST]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/test/test_provider.go b/cluster/clusterproviders/test/test_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..cf81319b5104f60ba71650cdc449f3b1b0c0ec52
--- /dev/null
+++ b/cluster/clusterproviders/test/test_provider.go
@@ -0,0 +1,223 @@
+package test
+
+import (
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "golang.org/x/exp/maps"
+)
+
+type ProviderConfig struct {
+ // ServiceTtl is the time to live for services. Default: 3s
+ ServiceTtl time.Duration
+ // RefreshTtl is the time between refreshes of the service ttl. Default: 1s
+ RefreshTtl time.Duration
+ // DeregisterCritical is the time after which a service is deregistered if it is not refreshed. Default: 10s
+ DeregisterCritical time.Duration
+}
+
+type ProviderOption func(config *ProviderConfig)
+
+// WithTestProviderServiceTtl sets the service ttl. Default: 3s
+func WithTestProviderServiceTtl(serviceTtl time.Duration) ProviderOption {
+ return func(config *ProviderConfig) {
+ config.ServiceTtl = serviceTtl
+ }
+}
+
+// WithTestProviderRefreshTtl sets the refresh ttl. Default: 1s
+func WithTestProviderRefreshTtl(refreshTtl time.Duration) ProviderOption {
+ return func(config *ProviderConfig) {
+ config.RefreshTtl = refreshTtl
+ }
+}
+
+// WithTestProviderDeregisterCritical sets the deregister critical. Default: 10s
+func WithTestProviderDeregisterCritical(deregisterCritical time.Duration) ProviderOption {
+ return func(config *ProviderConfig) {
+ config.DeregisterCritical = deregisterCritical
+ }
+}
+
+type Provider struct {
+ memberList *cluster.MemberList
+ config *ProviderConfig
+
+ agent *InMemAgent
+ id string
+ ttlReportTicker *time.Ticker
+}
+
+func NewTestProvider(agent *InMemAgent, options ...ProviderOption) *Provider {
+ config := &ProviderConfig{
+ ServiceTtl: time.Second * 3,
+ RefreshTtl: time.Second,
+ DeregisterCritical: time.Second * 10,
+ }
+ for _, option := range options {
+ option(config)
+ }
+ return &Provider{
+ config: config,
+ agent: agent,
+ }
+}
+
+func (t *Provider) StartMember(c *cluster.Cluster) error {
+ plog.Debug("start cluster member")
+ t.memberList = c.MemberList
+ host, port, err := c.ActorSystem.GetHostPort()
+ if err != nil {
+ return err
+ }
+ kinds := c.GetClusterKinds()
+ t.id = c.ActorSystem.ID
+ t.startTtlReport()
+ t.agent.SubscribeStatusUpdate(t.notifyStatuses)
+ t.agent.RegisterService(NewAgentServiceStatus(t.id, host, port, kinds))
+ return nil
+}
+
+func (t *Provider) StartClient(cluster *cluster.Cluster) error {
+ t.memberList = cluster.MemberList
+ t.id = cluster.ActorSystem.ID
+ t.agent.SubscribeStatusUpdate(t.notifyStatuses)
+ t.agent.ForceUpdate()
+ return nil
+}
+
+func (t *Provider) Shutdown(_ bool) error {
+ plog.Debug("Unregistering service", log.String("service", t.id))
+ if t.ttlReportTicker != nil {
+ t.ttlReportTicker.Stop()
+ }
+ t.agent.DeregisterService(t.id)
+ return nil
+}
+
+// notifyStatuses notifies the cluster that the service status has changed.
+func (t *Provider) notifyStatuses() {
+ statuses := t.agent.GetStatusHealth()
+
+ plog.Debug("TestAgent response", log.Object("statuses", statuses))
+ members := make([]*cluster.Member, 0, len(statuses))
+ for _, status := range statuses {
+ copiedKinds := make([]string, 0, len(status.Kinds))
+ copiedKinds = append(copiedKinds, status.Kinds...)
+
+ members = append(members, &cluster.Member{
+ Id: status.ID,
+ Port: int32(status.Port),
+ Host: status.Host,
+ Kinds: copiedKinds,
+ })
+ }
+ t.memberList.UpdateClusterTopology(members)
+}
+
+// startTtlReport starts the ttl report loop.
+func (t *Provider) startTtlReport() {
+ t.ttlReportTicker = time.NewTicker(t.config.RefreshTtl)
+ go func() {
+ for range t.ttlReportTicker.C {
+ t.agent.RefreshServiceTTL(t.id)
+ }
+ }()
+}
+
+type InMemAgent struct {
+ services map[string]AgentServiceStatus
+ servicesLock *sync.RWMutex
+
+ statusUpdateHandlers []func()
+ statusUpdateHandlersLock *sync.RWMutex
+}
+
+func NewInMemAgent() *InMemAgent {
+ return &InMemAgent{
+ services: make(map[string]AgentServiceStatus),
+ servicesLock: &sync.RWMutex{},
+ statusUpdateHandlers: make([]func(), 0),
+ statusUpdateHandlersLock: &sync.RWMutex{},
+ }
+}
+
+// RegisterService registers a AgentServiceStatus with the agent.
+func (m *InMemAgent) RegisterService(registration AgentServiceStatus) {
+ m.servicesLock.Lock()
+ m.services[registration.ID] = registration
+ m.servicesLock.Unlock()
+
+ m.onStatusUpdate()
+}
+
+// DeregisterService removes a service from the agent.
+func (m *InMemAgent) DeregisterService(id string) {
+ m.servicesLock.Lock()
+ delete(m.services, id)
+ m.servicesLock.Unlock()
+
+ m.onStatusUpdate()
+}
+
+// RefreshServiceTTL updates the TTL of all services.
+func (m *InMemAgent) RefreshServiceTTL(id string) {
+ m.servicesLock.Lock()
+ defer m.servicesLock.Unlock()
+ if service, ok := m.services[id]; ok {
+ service.TTL = time.Now()
+ m.services[id] = service
+ }
+}
+
+// SubscribeStatusUpdate registers a handler that will be called when the service map changes.
+func (m *InMemAgent) SubscribeStatusUpdate(handler func()) {
+ m.statusUpdateHandlersLock.Lock()
+ defer m.statusUpdateHandlersLock.Unlock()
+ m.statusUpdateHandlers = append(m.statusUpdateHandlers, handler)
+}
+
+// GetStatusHealth returns the health of the service.
+func (m *InMemAgent) GetStatusHealth() []AgentServiceStatus {
+ m.servicesLock.RLock()
+ defer m.servicesLock.RUnlock()
+ return maps.Values(m.services)
+}
+
+// ForceUpdate is used to trigger a status update event.
+func (m *InMemAgent) ForceUpdate() {
+ m.onStatusUpdate()
+}
+
+func (m *InMemAgent) onStatusUpdate() {
+ m.statusUpdateHandlersLock.RLock()
+ defer m.statusUpdateHandlersLock.RUnlock()
+ for _, handler := range m.statusUpdateHandlers {
+ handler()
+ }
+}
+
+type AgentServiceStatus struct {
+ ID string
+ TTL time.Time // last alive time
+ Host string
+ Port int
+ Kinds []string
+}
+
+// NewAgentServiceStatus creates a new AgentServiceStatus.
+func NewAgentServiceStatus(id string, host string, port int, kinds []string) AgentServiceStatus {
+ return AgentServiceStatus{
+ ID: id,
+ TTL: time.Now(),
+ Host: host,
+ Port: port,
+ Kinds: kinds,
+ }
+}
+
+func (a AgentServiceStatus) Alive() bool {
+ return time.Now().Sub(a.TTL) <= (time.Second * 5)
+}
diff --git a/cluster/clusterproviders/zk/config.go b/cluster/clusterproviders/zk/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..45e784cb87f555890e0d280e066356c4213785b6
--- /dev/null
+++ b/cluster/clusterproviders/zk/config.go
@@ -0,0 +1,87 @@
+package zk
+
+import (
+ "time"
+)
+
+const baseKey = `/protoactor`
+
+type Option func(*config)
+
+// WithAuth set zk auth
+func WithAuth(scheme string, credential string) Option {
+ return func(o *config) {
+ o.Auth = authConfig{Scheme: scheme, Credential: credential}
+ }
+}
+
+// WithBaseKey set actors base key
+func WithBaseKey(key string) Option {
+ return func(o *config) {
+ if isStrBlank(key) {
+ o.BaseKey = baseKey
+ } else {
+ o.BaseKey = formatBaseKey(key)
+ }
+ }
+}
+
+// WithSessionTimeout set zk session timeout
+func WithSessionTimeout(tm time.Duration) Option {
+ return func(o *config) {
+ o.SessionTimeout = tm
+ }
+}
+
+// WithRoleChangedListener triggered on self role changed
+func WithRoleChangedListener(l RoleChangedListener) Option {
+ return func(o *config) {
+ o.RoleChanged = l
+ }
+}
+
+func WithRoleChangedFunc(f OnRoleChangedFunc) Option {
+ return func(o *config) {
+ o.RoleChanged = f
+ }
+}
+
+func withEndpoints(e []string) Option {
+ return func(o *config) {
+ o.Endpoints = e
+ }
+}
+
+type authConfig struct {
+ Scheme string
+ Credential string
+}
+
+func (za authConfig) isEmpty() bool {
+ return za.Scheme == "" && za.Credential == ""
+}
+
+type RoleChangedListener interface {
+ OnRoleChanged(RoleType)
+}
+
+type OnRoleChangedFunc func(RoleType)
+
+func (fn OnRoleChangedFunc) OnRoleChanged(rt RoleType) {
+ fn(rt)
+}
+
+type config struct {
+ BaseKey string
+ Endpoints []string
+ SessionTimeout time.Duration
+ Auth authConfig
+ RoleChanged RoleChangedListener
+}
+
+func defaultConfig() *config {
+ return &config{
+ BaseKey: baseKey,
+ SessionTimeout: time.Second * 10,
+ }
+}
diff --git a/cluster/clusterproviders/zk/log.go b/cluster/clusterproviders/zk/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdeea87ee2b0d713b9b559ab1fe292ba9da9d8c0
--- /dev/null
+++ b/cluster/clusterproviders/zk/log.go
@@ -0,0 +1,11 @@
+package zk
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.InfoLevel, "[CLU/ZK]")
+
+// SetLogLevel sets the log level for the logger
+// SetLogLevel is safe to be called concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/clusterproviders/zk/misc_test.go b/cluster/clusterproviders/zk/misc_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..67627d57e03283d92faf3d53f4e9381d76d8d295
--- /dev/null
+++ b/cluster/clusterproviders/zk/misc_test.go
@@ -0,0 +1,103 @@
+package zk
+
+import (
+ "strings"
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/suite"
+)
+
+func (suite *MiscTestSuite) TestIntToStr() {
+ suite.Equal("100", intToStr(100))
+}
+
+func (suite *MiscTestSuite) TestStrToInt() {
+ suite.Equal(100, strToInt("100"))
+ suite.Equal(0, strToInt("str0"))
+}
+
+func (suite *MiscTestSuite) TestIsStrBlank() {
+ suite.True(isStrBlank(""))
+ suite.False(isStrBlank("e"))
+}
+
+func (suite *MiscTestSuite) TestFormatBaseKey() {
+ suite.Equal("/", formatBaseKey(""))
+ suite.Equal("/a/b/c", formatBaseKey("a/b/c"))
+ suite.Equal("/", formatBaseKey("/"))
+ suite.Equal("/a", formatBaseKey("a/"))
+}
+
+func (suite *MiscTestSuite) TestParseSeq() {
+ seq, err := parseSeq("/proto.actors/dev/my_cluster/_c_f4245284934bdaf384102b6fc233bd14-actor-0000000042")
+ suite.Nil(err)
+ suite.Equal(42, seq)
+}
+
+func (suite *MiscTestSuite) TestStringContains() {
+ suite.True(stringContains([]string{"a", "b"}, "b"))
+ suite.False(stringContains([]string{"a", "b"}, "c"))
+}
+
+func (suite *MiscTestSuite) TestMapString() {
+ orig := []string{"A", "B", "C"}
+ suite.ElementsMatch([]string{"a", "b", "c"}, mapString(orig, strings.ToLower))
+ suite.ElementsMatch([]string{"A", "B", "C"}, orig)
+}
+
+func (suite *MiscTestSuite) TestSafeRun() {
+ suite.NotPanics(func() { safeRun(func() { panic("don't worry, should panic here") }) })
+}
+
+func (suite *MiscTestSuite) TestNode() {
+ node := NewNode("pod1", "192.168.0.1", 7788, []string{"kind1", "kind2"})
+ host, port := node.GetAddress()
+ suite.Equal("192.168.0.1", host)
+ suite.Equal(7788, port)
+
+ suite.Equal("192.168.0.1:7788", node.GetAddressString())
+
+ suite.True(NewNode("pod1", "", 0, nil).Equal(node))
+
+ _, ok := node.GetMeta("key")
+ suite.False(ok)
+
+ node.SetMeta(metaKeySeq, "100")
+ suite.Equal(100, node.GetSeq())
+
+ suite.Equal(&cluster.Member{
+ Id: "pod1",
+ Host: "192.168.0.1",
+ Port: int32(7788),
+ Kinds: []string{"kind1", "kind2"},
+ }, node.MemberStatus())
+
+ data, err := node.Serialize()
+ suite.Nil(err)
+ suite.Equal(`{"id":"pod1","name":"pod1","host":"192.168.0.1","address":"192.168.0.1","port":7788,"kinds":["kind1","kind2"],"alive":true}`, string(data))
+
+ node2 := &Node{}
+ err = node2.Deserialize([]byte(`{"id":"pod1","name":"pod1","host":"192.168.0.1","address":"192.168.0.1","port":7788,"kinds":["kind1","kind2"],"alive":true}`))
+ suite.Nil(err)
+ node.Meta = nil
+ suite.Equal(node2, node)
+}
+
+type MiscTestSuite struct {
+ suite.Suite
+ ctrl *gomock.Controller
+}
+
+func (suite *MiscTestSuite) SetupTest() {
+ suite.ctrl = gomock.NewController(suite.T())
+}
+
+func (suite *MiscTestSuite) TearDownTest() {
+ suite.ctrl.Finish()
+}
+
+func TestMiscTestSuite(t *testing.T) {
+ suite.Run(t, new(MiscTestSuite))
+}
diff --git a/cluster/clusterproviders/zk/node.go b/cluster/clusterproviders/zk/node.go
new file mode 100644
index 0000000000000000000000000000000000000000..e55846066258857791a2cdc1b6fbf7389749c563
--- /dev/null
+++ b/cluster/clusterproviders/zk/node.go
@@ -0,0 +1,109 @@
+package zk
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+const (
+ metaKeyID = "id"
+ metaKeySeq = "seq"
+)
+
+type Node struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Host string `json:"host"`
+ Address string `json:"address"`
+ Port int `json:"port"`
+ Kinds []string `json:"kinds"`
+ Meta map[string]string `json:"-"`
+ Alive bool `json:"alive"`
+}
+
+func NewNode(name, host string, port int, kinds []string) *Node {
+ return &Node{
+ ID: name,
+ Name: name,
+ Address: host,
+ Host: host,
+ Port: port,
+ Kinds: kinds,
+ Meta: map[string]string{},
+ Alive: true,
+ }
+}
+
+func (n *Node) GetAddress() (host string, port int) {
+ host = n.Host
+ port = n.Port
+ if host == "" {
+ host = n.Address
+ }
+ return
+}
+
+func (n *Node) GetAddressString() string {
+ h, p := n.GetAddress()
+ return fmt.Sprintf("%v:%v", h, p)
+}
+
+func (n *Node) Equal(other *Node) bool {
+ if n == nil || other == nil {
+ return false
+ }
+ if n == other {
+ return true
+ }
+ return n.ID == other.ID
+}
+
+func (n *Node) GetMeta(name string) (string, bool) {
+ if n.Meta == nil {
+ return "", false
+ }
+ val, ok := n.Meta[name]
+ return val, ok
+}
+
+func (n *Node) GetSeq() int {
+ if seqStr, ok := n.GetMeta(metaKeySeq); ok {
+ return strToInt(seqStr)
+ }
+ return 0
+}
+
+func (n *Node) MemberStatus() *cluster.Member {
+ host, port := n.GetAddress()
+ kinds := n.Kinds
+ if kinds == nil {
+ kinds = []string{}
+ }
+ return &cluster.Member{
+ Id: n.ID,
+ Host: host,
+ Port: int32(port),
+ Kinds: kinds,
+ }
+}
+
+func (n *Node) SetMeta(name string, val string) {
+ if n.Meta == nil {
+ n.Meta = map[string]string{}
+ }
+ n.Meta[name] = val
+}
+
+func (n *Node) Serialize() ([]byte, error) {
+ data, err := json.Marshal(n)
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
+func (n *Node) Deserialize(data []byte) error {
+ return json.Unmarshal(data, n)
+}
diff --git a/cluster/clusterproviders/zk/singleton.go b/cluster/clusterproviders/zk/singleton.go
new file mode 100644
index 0000000000000000000000000000000000000000..280898e89d1e5427169cd7b67be39a80dbbcd158
--- /dev/null
+++ b/cluster/clusterproviders/zk/singleton.go
@@ -0,0 +1,54 @@
+package zk
+
+import (
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type SingletonScheduler struct {
+ sync.Mutex
+ root *actor.RootContext
+ props []*actor.Props
+ pids []*actor.PID
+}
+
+func NewSingletonScheduler(rc *actor.RootContext) *SingletonScheduler {
+ return &SingletonScheduler{root: rc}
+}
+
+func (s *SingletonScheduler) FromFunc(f actor.ReceiveFunc) *SingletonScheduler {
+ s.Lock()
+ defer s.Unlock()
+ s.props = append(s.props, actor.PropsFromFunc(f))
+ return s
+}
+
+func (s *SingletonScheduler) FromProducer(f actor.Producer) *SingletonScheduler {
+ s.Lock()
+ defer s.Unlock()
+ s.props = append(s.props, actor.PropsFromProducer(f))
+ return s
+}
+
+func (s *SingletonScheduler) OnRoleChanged(rt RoleType) {
+ s.Lock()
+ defer s.Unlock()
+ if rt == Follower {
+ if len(s.pids) > 0 {
+ plog.Info("I am follower, poison singleton actors")
+ for _, pid := range s.pids {
+ s.root.Poison(pid)
+ }
+ s.pids = nil
+ }
+ } else if rt == Leader {
+ if len(s.props) > 0 {
+ plog.Info("I am leader now, start singleton actors")
+ s.pids = make([]*actor.PID, len(s.props))
+ for i, p := range s.props {
+ s.pids[i] = s.root.Spawn(p)
+ }
+ }
+ }
+}
diff --git a/cluster/clusterproviders/zk/utils.go b/cluster/clusterproviders/zk/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..158672b068d944e70ac2410479389fd3f7ad0b3c
--- /dev/null
+++ b/cluster/clusterproviders/zk/utils.go
@@ -0,0 +1,83 @@
+package zk
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+func intToStr(i int) string {
+ return strconv.FormatInt(int64(i), 10)
+}
+
+func strToInt(s string) int {
+ i, _ := strconv.ParseInt(s, 10, 64)
+ return int(i)
+}
+
+func isStrBlank(s string) bool { return s == "" }
+
+func formatBaseKey(s string) string {
+ if !strings.HasPrefix(s, "/") {
+ s = "/" + s
+ }
+ if strings.HasSuffix(s, "/") && s != "/" {
+ s = strings.TrimSuffix(s, "/")
+ }
+ return s
+}
+
+func parseSeq(path string) (int, error) {
+ parts := strings.Split(path, "-")
+ if len(parts) == 1 {
+ parts = strings.Split(path, "__")
+ }
+ return strconv.Atoi(parts[len(parts)-1])
+}
+
+func stringContains(list []string, str string) bool {
+ for _, v := range list {
+ if v == str {
+ return true
+ }
+ }
+ return false
+}
+
+func mapString(list []string, fn func(string) string) []string {
+ l := make([]string, len(list))
+ for i, str := range list {
+ l[i] = fn(str)
+ }
+ return l
+}
+
+func safeRun(fn func()) {
+ defer func() {
+ if r := recover(); r != nil {
+ plog.Warn("OnRoleChanged.", log.Error(fmt.Errorf("%v\n%s", r, string(getRunTimeStack()))))
+ }
+ }()
+ fn()
+}
+
+func getRunTimeStack() []byte {
+ const size = 64 << 10
+ buf := make([]byte, size)
+ return buf[:runtime.Stack(buf, false)]
+}
+
+func getParentDir(path string) string {
+ parent := path[:strings.LastIndex(path, "/")]
+ if parent == "" {
+ return "/"
+ }
+ return parent
+}
+
+func joinPath(paths ...string) string {
+ return strings.Join(paths, "/")
+}
diff --git a/cluster/clusterproviders/zk/zk_conn.go b/cluster/clusterproviders/zk/zk_conn.go
new file mode 100644
index 0000000000000000000000000000000000000000..e1148020e2fac2de2f10b140cdfa5ab48674f495
--- /dev/null
+++ b/cluster/clusterproviders/zk/zk_conn.go
@@ -0,0 +1,94 @@
+package zk
+
+import (
+ "time"
+
+ "github.com/go-zookeeper/zk"
+)
+
+type zkConn interface {
+ AddAuth(scheme string, auth []byte) error
+ Exists(path string) (bool, *zk.Stat, error)
+ Create(path string, data []byte, flags int32, acl []zk.ACL) (string, error)
+ Delete(path string, version int32) error
+ Get(path string) ([]byte, *zk.Stat, error)
+ Children(path string) ([]string, *zk.Stat, error)
+ ChildrenW(path string) ([]string, *zk.Stat, <-chan zk.Event, error)
+ CreateProtectedEphemeralSequential(path string, data []byte, acl []zk.ACL) (string, error)
+ Close()
+}
+
+func connectZk(servers []string, sessionTimeout time.Duration, opts ...zkConnOpt) (zkConn, error) {
+ opt := newZkOptions(opts...)
+ var conn *zk.Conn
+ var err error
+ if opt.ecb != nil {
+ conn, _, err = zk.Connect(servers, sessionTimeout, zk.WithEventCallback(opt.ecb))
+ } else {
+ conn, _, err = zk.Connect(servers, sessionTimeout)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &zkConnImpl{conn: conn}, nil
+}
+
+type zkConnImpl struct {
+ conn *zk.Conn
+}
+
+func (impl *zkConnImpl) AddAuth(scheme string, auth []byte) error {
+ return impl.conn.AddAuth(scheme, auth)
+}
+
+func (impl *zkConnImpl) Exists(path string) (bool, *zk.Stat, error) {
+ return impl.conn.Exists(path)
+}
+
+func (impl *zkConnImpl) Create(path string, data []byte, flags int32, acl []zk.ACL) (string, error) {
+ return impl.conn.Create(path, data, flags, acl)
+}
+
+func (impl *zkConnImpl) Delete(path string, version int32) error {
+ return impl.conn.Delete(path, version)
+}
+
+func (impl *zkConnImpl) Get(path string) ([]byte, *zk.Stat, error) {
+ return impl.conn.Get(path)
+}
+
+func (impl *zkConnImpl) Children(path string) ([]string, *zk.Stat, error) {
+ return impl.conn.Children(path)
+}
+
+func (impl *zkConnImpl) ChildrenW(path string) ([]string, *zk.Stat, <-chan zk.Event, error) {
+ return impl.conn.ChildrenW(path)
+}
+
+func (impl *zkConnImpl) CreateProtectedEphemeralSequential(path string, data []byte, acl []zk.ACL) (string, error) {
+ return impl.conn.CreateProtectedEphemeralSequential(path, data, acl)
+}
+
+func (impl *zkConnImpl) Close() {
+ impl.conn.Close()
+}
+
+type zkoption struct {
+ ecb zk.EventCallback
+}
+
+func newZkOptions(opts ...zkConnOpt) *zkoption {
+ opt := &zkoption{}
+ for _, fn := range opts {
+ fn(opt)
+ }
+ return opt
+}
+
+type zkConnOpt func(*zkoption)
+
+func WithEventCallback(cb zk.EventCallback) zkConnOpt {
+ return func(o *zkoption) {
+ o.ecb = cb
+ }
+}
diff --git a/cluster/clusterproviders/zk/zk_provider.go b/cluster/clusterproviders/zk/zk_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..cc737cb2fb5eb4e4723799ecd810b71c6645265e
--- /dev/null
+++ b/cluster/clusterproviders/zk/zk_provider.go
@@ -0,0 +1,523 @@
+package zk
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/go-zookeeper/zk"
+)
+
+var _ cluster.ClusterProvider = new(Provider)
+
+type RoleType int
+
+const (
+ Follower RoleType = iota
+ Leader
+)
+
+func (r RoleType) String() string {
+ if r == Leader {
+ return "LEADER"
+ }
+ return "FOLLOWER"
+}
+
+type Provider struct {
+ cluster *cluster.Cluster
+ baseKey string
+ clusterName string
+ clusterKey string
+ deregistered bool
+ shutdown bool
+ self *Node
+ members map[string]*Node // all, contains self.
+ clusterError error
+ conn zkConn
+ revision uint64
+ fullpath string
+ roleChangedListener RoleChangedListener
+ role RoleType
+ roleChangedChan chan RoleType
+}
+
+// New zk cluster provider with config
+func New(endpoints []string, opts ...Option) (*Provider, error) {
+ zkCfg := defaultConfig()
+ withEndpoints(endpoints)(zkCfg)
+ for _, fn := range opts {
+ fn(zkCfg)
+ }
+ p := &Provider{
+ cluster: &cluster.Cluster{},
+ baseKey: zkCfg.BaseKey,
+ clusterKey: "",
+ clusterName: "",
+ deregistered: false,
+ shutdown: false,
+ self: &Node{},
+ members: map[string]*Node{},
+ revision: 0,
+ fullpath: "",
+ roleChangedListener: zkCfg.RoleChanged,
+ roleChangedChan: make(chan RoleType, 1),
+ role: Follower,
+ }
+ conn, err := connectZk(endpoints, zkCfg.SessionTimeout, WithEventCallback(p.onEvent))
+ if err != nil {
+ plog.Error("connect zk fail", log.Error(err))
+ return nil, err
+ }
+ if auth := zkCfg.Auth; !auth.isEmpty() {
+ if err = conn.AddAuth(auth.Scheme, []byte(auth.Credential)); err != nil {
+ plog.Error("auth failure.", log.String("scheme", auth.Scheme), log.String("cred", auth.Credential), log.Error(err))
+ return nil, err
+ }
+ }
+ p.conn = conn
+
+ return p, nil
+}
+
+func (p *Provider) IsLeader() bool {
+ return p.role == Leader
+}
+
+func (p *Provider) init(c *cluster.Cluster) error {
+ p.cluster = c
+ addr := p.cluster.ActorSystem.Address()
+ host, port, err := splitHostPort(addr)
+ if err != nil {
+ return err
+ }
+
+ p.cluster = c
+ p.clusterName = p.cluster.Config.Name
+ p.clusterKey = joinPath(p.baseKey, p.clusterName)
+ knownKinds := c.GetClusterKinds()
+ nodeName := fmt.Sprintf("%v@%v:%v", p.clusterName, host, port)
+ p.self = NewNode(nodeName, host, port, knownKinds)
+ p.self.SetMeta(metaKeyID, p.getID())
+
+ if err = p.createClusterNode(p.clusterKey); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (p *Provider) StartMember(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ plog.Error("init fail " + err.Error())
+ return err
+ }
+
+ p.startRoleChangedNotifyLoop()
+
+ // register self
+ if err := p.registerService(); err != nil {
+ plog.Error("register service fail " + err.Error())
+ return err
+ }
+ plog.Info("StartMember register service.", log.String("node", p.self.ID), log.String("seq", p.self.Meta[metaKeySeq]))
+
+ // fetch member list
+ nodes, version, err := p.fetchNodes()
+ if err != nil {
+ plog.Error("fetch nodes fail " + err.Error())
+ return err
+ }
+ // initialize members
+ p.updateNodesWithSelf(nodes, version)
+ p.publishClusterTopologyEvent()
+ p.updateLeadership(nodes)
+ p.startWatching(true)
+
+ return nil
+}
+
+func (p *Provider) StartClient(c *cluster.Cluster) error {
+ if err := p.init(c); err != nil {
+ return err
+ }
+ nodes, version, err := p.fetchNodes()
+ if err != nil {
+ return err
+ }
+ // initialize members
+ p.updateNodes(nodes, version)
+ p.publishClusterTopologyEvent()
+ p.startWatching(false)
+
+ return nil
+}
+
+func (p *Provider) Shutdown(graceful bool) error {
+ p.shutdown = true
+ if !p.deregistered {
+ p.updateLeadership(nil)
+ err := p.deregisterService()
+ if err != nil {
+ plog.Error("deregisterMember", log.Error(err))
+ return err
+ }
+ p.deregistered = true
+ }
+ return nil
+}
+
+func (p *Provider) getID() string {
+ return p.self.ID
+}
+
+func (p *Provider) registerService() error {
+ data, err := p.self.Serialize()
+ if err != nil {
+ plog.Error("registerService Serialize fail.", log.Error(err))
+ return err
+ }
+
+ path, err := p.createEphemeralChildNode(data)
+ if err != nil {
+ plog.Error("createEphemeralChildNode fail.", log.String("node", p.clusterKey), log.Error(err))
+ return err
+ }
+ p.fullpath = path
+ seq, _ := parseSeq(path)
+ p.self.SetMeta(metaKeySeq, intToStr(seq))
+ plog.Info("RegisterService.", log.String("id", p.self.ID), log.Int("seq", seq))
+
+ return nil
+}
+
+func (p *Provider) createClusterNode(dir string) error {
+ if dir == "/" {
+ return nil
+ }
+ exist, _, err := p.conn.Exists(dir)
+ if err != nil {
+ plog.Error("check exist of node fail", log.String("dir", dir), log.Error(err))
+ return err
+ }
+ if exist {
+ return nil
+ }
+ if err = p.createClusterNode(getParentDir(dir)); err != nil {
+ return err
+ }
+ if _, err = p.conn.Create(dir, []byte{}, 0, zk.WorldACL(zk.PermAll)); err != nil {
+ plog.Error("create dir node fail", log.String("dir", dir), log.Error(err))
+ return err
+ }
+ return nil
+}
+
+func (p *Provider) deregisterService() error {
+ if p.fullpath != "" {
+ p.conn.Delete(p.fullpath, -1)
+ }
+ p.fullpath = ""
+ p.conn.Close()
+ return nil
+}
+
+func (p *Provider) keepWatching(ctx context.Context, registerSelf bool) error {
+ evtChan, err := p.addWatcher(ctx, p.clusterKey)
+ if err != nil {
+ plog.Error("list children fail", log.String("node", p.clusterKey), log.Error(err))
+ return err
+ }
+
+ return p._keepWatching(registerSelf, evtChan)
+}
+
+func (p *Provider) addWatcher(ctx context.Context, clusterKey string) (<-chan zk.Event, error) {
+ _, stat, evtChan, err := p.conn.ChildrenW(clusterKey)
+ if err != nil {
+ plog.Error("list children fail", log.String("node", clusterKey), log.Error(err))
+ return nil, err
+ }
+
+ plog.Info("KeepWatching cluster.", log.String("cluster", clusterKey), log.Int("children", int(stat.NumChildren)))
+ if !p.isChildrenChanged(ctx, stat) {
+ return evtChan, nil
+ }
+
+ plog.Info("Chilren changed, wait 1 sec and watch again", log.Int("old_cversion", int(p.revision)), log.Int("new_revison", int(stat.Cversion)))
+ time.Sleep(1 * time.Second)
+ nodes, version, err := p.fetchNodes()
+ if err != nil {
+ return nil, err
+ }
+ // initialize members
+ p.updateNodes(nodes, version)
+ p.publishClusterTopologyEvent()
+ p.updateLeadership(nodes)
+ return p.addWatcher(ctx, clusterKey)
+}
+
+func (p *Provider) isChildrenChanged(ctx context.Context, stat *zk.Stat) bool {
+ return stat.Cversion != int32(p.revision)
+}
+
+func (p *Provider) _keepWatching(registerSelf bool, stream <-chan zk.Event) error {
+ event := <-stream
+ if err := event.Err; err != nil {
+ plog.Error("Failure watching service.", log.Error(err))
+ if registerSelf && p.clusterNotContainsSelfPath() {
+ plog.Info("Register info lost, register self again")
+ p.registerService()
+ }
+ return err
+ }
+ nodes, version, err := p.fetchNodes()
+ if err != nil {
+ plog.Error("Failure fetch nodes when watching service.", log.Error(err))
+ return err
+ }
+ if !p.containSelf(nodes) && registerSelf {
+ // i am lost, register self
+ if err = p.registerService(); err != nil {
+ return err
+ }
+ // reload nodes
+ nodes, version, err = p.fetchNodes()
+ if err != nil {
+ plog.Error("Failure fetch nodes when watching service.", log.Error(err))
+ return err
+ }
+ }
+ p.updateNodes(nodes, version)
+ p.publishClusterTopologyEvent()
+ if registerSelf {
+ p.updateLeadership(nodes)
+ }
+
+ return nil
+}
+
+func (p *Provider) clusterNotContainsSelfPath() bool {
+ children, _, err := p.conn.Children(p.clusterKey)
+ return err == nil && !stringContains(mapString(children, func(s string) string {
+ return joinPath(p.clusterKey, s)
+ }), p.fullpath)
+}
+
+func (p *Provider) containSelf(ns []*Node) bool {
+ for _, node := range ns {
+ if p.self != nil && node.ID == p.self.ID {
+ return true
+ }
+ }
+ return false
+}
+
+func (p *Provider) startRoleChangedNotifyLoop() {
+ go func() {
+ for !p.shutdown {
+ role := <-p.roleChangedChan
+ if lis := p.roleChangedListener; lis != nil {
+ safeRun(func() { lis.OnRoleChanged(role) })
+ }
+ }
+ }()
+}
+
+func (p *Provider) updateLeadership(ns []*Node) {
+ role := Follower
+ if p.isLeaderOf(ns) {
+ role = Leader
+ }
+ if role != p.role {
+ plog.Info("Role changed.", log.String("from", p.role.String()), log.String("to", role.String()))
+ p.role = role
+ p.roleChangedChan <- role
+ }
+}
+
+func (p *Provider) onEvent(evt zk.Event) {
+ plog.Debug("Zookeeper event.", log.String("type", evt.Type.String()), log.String("state", evt.State.String()), log.String("path", evt.Path))
+ if evt.Type != zk.EventSession {
+ return
+ }
+ switch evt.State {
+ case zk.StateConnecting, zk.StateDisconnected, zk.StateExpired:
+ if p.role == Leader {
+ plog.Info("Role changed.", log.String("from", Leader.String()), log.String("to", Follower.String()))
+ p.role = Follower
+ p.roleChangedChan <- Follower
+ }
+ case zk.StateConnected, zk.StateHasSession:
+ }
+}
+
+func (p *Provider) isLeaderOf(ns []*Node) bool {
+ if len(ns) == 1 && p.self != nil && ns[0].ID == p.self.ID {
+ return true
+ }
+ var minSeq int
+ for _, node := range ns {
+ if seq := node.GetSeq(); (seq > 0 && seq < minSeq) || minSeq == 0 {
+ minSeq = seq
+ }
+ }
+ for _, node := range ns {
+ if p.self != nil && node.ID == p.self.ID {
+ return minSeq > 0 && minSeq == p.self.GetSeq()
+ }
+ }
+ return false
+}
+
+func (p *Provider) startWatching(registerSelf bool) {
+ ctx := context.TODO()
+ go func() {
+ for !p.shutdown {
+ if err := p.keepWatching(ctx, registerSelf); err != nil {
+ plog.Error("Failed to keepWatching.", log.Error(err))
+ p.clusterError = err
+ }
+ }
+ }()
+}
+
+// GetHealthStatus returns an error if the cluster health status has problems
+func (p *Provider) GetHealthStatus() error {
+ return p.clusterError
+}
+
+func (p *Provider) fetchNodes() ([]*Node, int32, error) {
+ children, stat, err := p.conn.Children(p.clusterKey)
+ if err != nil {
+ plog.Error("FetchNodes fail.", log.String("node", p.clusterKey), log.Error(err))
+ return nil, 0, err
+ }
+
+ var nodes []*Node
+ for _, short := range children {
+ long := joinPath(p.clusterKey, short)
+ value, _, err := p.conn.Get(long)
+ if err != nil {
+ plog.Error("FetchNodes fail.", log.String("node", long), log.Error(err))
+ return nil, stat.Cversion, err
+ }
+ n := Node{Meta: make(map[string]string)}
+ if err := n.Deserialize(value); err != nil {
+ plog.Error("FetchNodes Deserialize fail.", log.String("node", long), log.String("val", string(value)), log.Error(err))
+ return nil, stat.Cversion, err
+ }
+ seq, err := parseSeq(long)
+ if err != nil {
+ plog.Error("FetchNodes parse seq fail.", log.String("node", long), log.String("val", string(value)), log.Error(err))
+ } else {
+ n.SetMeta(metaKeySeq, intToStr(seq))
+ }
+ plog.Info("FetchNodes new node.", log.String("id", n.ID), log.String("path", long), log.Int("seq", seq))
+ nodes = append(nodes, &n)
+ }
+ return p.uniqNodes(nodes), stat.Cversion, nil
+}
+
+func (p *Provider) updateNodes(members []*Node, reversion int32) {
+ nm := make(map[string]*Node)
+ for _, n := range members {
+ nm[n.ID] = n
+ }
+ p.members = nm
+ p.revision = uint64(reversion)
+}
+
+func (p *Provider) uniqNodes(nodes []*Node) []*Node {
+ nodeMap := make(map[string]*Node)
+ for _, node := range nodes {
+ if n, ok := nodeMap[node.GetAddressString()]; ok {
+ // keep node with higher version
+ if node.GetSeq() > n.GetSeq() {
+ nodeMap[node.GetAddressString()] = node
+ }
+ } else {
+ nodeMap[node.GetAddressString()] = node
+ }
+ }
+
+ var out []*Node
+ for _, node := range nodeMap {
+ out = append(out, node)
+ }
+ return out
+}
+
+func (p *Provider) updateNodesWithSelf(members []*Node, version int32) {
+ p.updateNodes(members, version)
+ p.members[p.self.ID] = p.self
+}
+
+func (p *Provider) createClusterTopologyEvent() []*cluster.Member {
+ res := make([]*cluster.Member, len(p.members))
+ i := 0
+ for _, m := range p.members {
+ res[i] = m.MemberStatus()
+ i++
+ }
+ return res
+}
+
+func (p *Provider) publishClusterTopologyEvent() {
+ res := p.createClusterTopologyEvent()
+ plog.Info("Update cluster.", log.Int("members", len(res)))
+ p.cluster.MemberList.UpdateClusterTopology(res)
+}
+
+func splitHostPort(addr string) (host string, port int, err error) {
+ if h, p, e := net.SplitHostPort(addr); e != nil {
+ if addr != "nonhost" {
+ err = e
+ }
+ host = "nonhost"
+ port = -1
+ } else {
+ host = h
+ port, err = strconv.Atoi(p)
+ }
+ return
+}
+
+func (pro *Provider) createEphemeralChildNode(data []byte) (string, error) {
+ acl := zk.WorldACL(zk.PermAll)
+ prefix := joinPath(pro.clusterKey, "actor-")
+ path := ""
+ var err error
+ for i := 0; i < 3; i++ {
+ path, err = pro.conn.CreateProtectedEphemeralSequential(prefix, data, acl)
+ if err == zk.ErrNoNode {
+ // Create parent node.
+ parts := strings.Split(pro.clusterKey, "/")
+ pth := ""
+ for _, p := range parts[1:] {
+ var exists bool
+ pth += "/" + p
+ exists, _, err = pro.conn.Exists(pth)
+ if err != nil {
+ return "", err
+ }
+ if exists == true {
+ continue
+ }
+ _, err = pro.conn.Create(pth, []byte{}, 0, acl)
+ if err != nil && err != zk.ErrNodeExists {
+ return "", err
+ }
+ }
+ } else if err == nil {
+ break
+ } else {
+ return "", err
+ }
+ }
+ return path, err
+}
diff --git a/cluster/clusterproviders/zk/zk_provider_test.go b/cluster/clusterproviders/zk/zk_provider_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d8adf53e18827551a0e73802526c71cb7ff6bd7
--- /dev/null
+++ b/cluster/clusterproviders/zk/zk_provider_test.go
@@ -0,0 +1,77 @@
+package zk
+
+import (
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/cluster/identitylookup/disthash"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "github.com/stretchr/testify/suite"
+)
+
+type ZookeeperTestSuite struct {
+ suite.Suite
+}
+
+func (suite *ZookeeperTestSuite) SetupTest() {
+ plog.SetLevel(log.ErrorLevel)
+}
+
+func (suite *ZookeeperTestSuite) TearDownTest() {
+}
+
+func TestZookeeperTestSuite(t *testing.T) {
+ suite.Run(t, new(ZookeeperTestSuite))
+}
+
+type ClusterAndSystem struct {
+ Cluster *cluster.Cluster
+ System *actor.ActorSystem
+}
+
+func (self *ClusterAndSystem) Shutdown() {
+ self.Cluster.Shutdown(true)
+}
+
+func (suite *ZookeeperTestSuite) start(name string, opts ...cluster.ConfigOption) *ClusterAndSystem {
+ cp, _ := New([]string{`localhost:8000`})
+ remoteConfig := remote.Configure("localhost", 0)
+ config := cluster.Configure(name, cp, disthash.New(), remoteConfig, opts...)
+ system := actor.NewActorSystem()
+ c := cluster.New(system, config)
+ c.StartMember()
+ return &ClusterAndSystem{Cluster: c, System: system}
+}
+
+func (suite *ZookeeperTestSuite) TestEmptyExecute() {
+ name := `cluster0`
+ suite.start(name).Shutdown()
+}
+
+func (suite *ZookeeperTestSuite) TestMultiNodes() {
+ var actorCount int32
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch ctx.Message().(type) {
+ case *actor.Started:
+ atomic.AddInt32(&actorCount, 1)
+ }
+ })
+ helloKind := cluster.NewKind("hello", props)
+
+ name := `cluster1`
+ c1 := suite.start(name, cluster.WithKinds(helloKind))
+ defer c1.Shutdown()
+ c2 := suite.start(name, cluster.WithKinds(helloKind))
+ defer c2.Shutdown()
+ c1.Cluster.Get(`a1`, `hello`)
+ c2.Cluster.Get(`a2`, `hello`)
+ for actorCount != 2 {
+ time.Sleep(time.Microsecond * 5)
+ }
+ suite.Assert().Equal(2, c1.Cluster.MemberList.Members().Len(), "Expected 2 members in the cluster")
+ suite.Assert().Equal(2, c2.Cluster.MemberList.Members().Len(), "Expected 2 members in the cluster")
+}
diff --git a/cluster/config.go b/cluster/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..c343f8a35c2a7b38e07d120233cf9ae94548d8e9
--- /dev/null
+++ b/cluster/config.go
@@ -0,0 +1,137 @@
+package cluster
+
+import (
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+type Config struct {
+ Name string
+ Address string
+ ClusterProvider ClusterProvider
+ IdentityLookup IdentityLookup
+ RemoteConfig *remote.Config
+ RequestTimeoutTime time.Duration
+ RequestsLogThrottlePeriod time.Duration
+ MaxNumberOfEventsInRequestLogThrottledPeriod int
+ ClusterContextProducer ContextProducer
+ MemberStrategyBuilder func(cluster *Cluster, kind string) MemberStrategy
+ Kinds map[string]*Kind
+ TimeoutTime time.Duration
+ GossipInterval time.Duration
+ GossipRequestTimeout time.Duration
+ GossipFanOut int
+ GossipMaxSend int
+ HeartbeatExpiration time.Duration // Gossip heartbeat timeout. If the member does not update its heartbeat within this period, it will be added to the BlockList
+ PubSubConfig *PubSubConfig
+}
+
+func Configure(clusterName string, clusterProvider ClusterProvider, identityLookup IdentityLookup, remoteConfig *remote.Config, options ...ConfigOption) *Config {
+ config := &Config{
+ Name: clusterName,
+ ClusterProvider: clusterProvider,
+ IdentityLookup: identityLookup,
+ RequestTimeoutTime: defaultActorRequestTimeout,
+ RequestsLogThrottlePeriod: defaultRequestsLogThrottlePeriod,
+ MemberStrategyBuilder: newDefaultMemberStrategy,
+ RemoteConfig: remoteConfig,
+ Kinds: make(map[string]*Kind),
+ ClusterContextProducer: newDefaultClusterContext,
+ MaxNumberOfEventsInRequestLogThrottledPeriod: defaultMaxNumberOfEvetsInRequestLogThrottledPeriod,
+ TimeoutTime: time.Second * 5,
+ GossipInterval: time.Millisecond * 300,
+ GossipRequestTimeout: time.Millisecond * 500,
+ GossipFanOut: 3,
+ GossipMaxSend: 50,
+ HeartbeatExpiration: time.Second * 20,
+ PubSubConfig: newPubSubConfig(),
+ }
+
+ for _, option := range options {
+ option(config)
+ }
+
+ return config
+}
+
+// ToClusterContextConfig converts this cluster Config Context parameters
+// into a valid ClusterContextConfig value and returns a pointer to its memory
+func (c *Config) ToClusterContextConfig() *ClusterContextConfig {
+ clusterContextConfig := ClusterContextConfig{
+ ActorRequestTimeout: c.RequestTimeoutTime,
+ RequestsLogThrottlePeriod: c.RequestsLogThrottlePeriod,
+ MaxNumberOfEventsInRequestLogThrottledPeriod: c.MaxNumberOfEventsInRequestLogThrottledPeriod,
+ RetryAction: defaultRetryAction,
+ requestLogThrottle: actor.NewThrottle(
+ int32(defaultMaxNumberOfEvetsInRequestLogThrottledPeriod),
+ defaultRequestsLogThrottlePeriod,
+ func(i int32) {
+ plog.Info(fmt.Sprintf("Throttled %d Request logs", i))
+ },
+ ),
+ }
+ return &clusterContextConfig
+}
+
+func WithClusterIdentity(props *actor.Props, ci *ClusterIdentity) *actor.Props {
+ // inject the cluster identity into the actor context
+ p := props.Clone(
+ actor.WithOnInit(func(ctx actor.Context) {
+ SetClusterIdentity(ctx, ci)
+ }))
+ return p
+}
+
+func withClusterReceiveMiddleware() actor.PropsOption {
+ return actor.WithReceiverMiddleware(func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ return func(c actor.ReceiverContext, envelope *actor.MessageEnvelope) {
+ // the above code as a type switch
+ switch envelope.Message.(type) {
+ case *actor.Started:
+ handleStarted(c, next, envelope)
+ case *actor.Stopped:
+ handleStopped(c, next, envelope)
+ default:
+ next(c, envelope)
+ }
+
+ return
+ }
+ })
+}
+
+func handleStopped(c actor.ReceiverContext, next actor.ReceiverFunc, envelope *actor.MessageEnvelope) {
+ /*
+ clusterKind.Dec();
+ */
+ cl := GetCluster(c.ActorSystem())
+ identity := GetClusterIdentity(c)
+
+ if identity != nil {
+ cl.ActorSystem.EventStream.Publish(&ActivationTerminating{
+ Pid: c.Self(),
+ ClusterIdentity: identity,
+ })
+ cl.PidCache.RemoveByValue(identity.Identity, identity.Kind, c.Self())
+ }
+
+ next(c, envelope)
+}
+
+func handleStarted(c actor.ReceiverContext, next actor.ReceiverFunc, envelope *actor.MessageEnvelope) {
+ next(c, envelope)
+ cl := GetCluster(c.ActorSystem())
+ identity := GetClusterIdentity(c)
+
+ grainInit := &ClusterInit{
+ Identity: identity,
+ Cluster: cl,
+ }
+
+ ge := actor.WrapEnvelope(grainInit)
+ next(c, ge)
+}
diff --git a/cluster/config_opts.go b/cluster/config_opts.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b3d23b3025b1083866e4876c683ca4f11181991
--- /dev/null
+++ b/cluster/config_opts.go
@@ -0,0 +1,56 @@
+package cluster
+
+import "time"
+
+type ConfigOption func(config *Config)
+
+// WithRequestTimeout sets the request timeout.
+func WithRequestTimeout(t time.Duration) ConfigOption {
+ return func(c *Config) {
+ c.RequestTimeoutTime = t
+ }
+}
+
+// WithRequestsLogThrottlePeriod sets the requests log throttle period.
+func WithRequestsLogThrottlePeriod(period time.Duration) ConfigOption {
+ return func(c *Config) {
+ c.RequestsLogThrottlePeriod = period
+ }
+}
+
+// WithClusterContextProducer sets the cluster context producer.
+func WithClusterContextProducer(producer ContextProducer) ConfigOption {
+ return func(c *Config) {
+ c.ClusterContextProducer = producer
+ }
+}
+
+// WithMaxNumberOfEventsInRequestLogThrottlePeriod sets the max number of events in request log throttled period.
+func WithMaxNumberOfEventsInRequestLogThrottlePeriod(maxNumber int) ConfigOption {
+ return func(c *Config) {
+ c.MaxNumberOfEventsInRequestLogThrottledPeriod = maxNumber
+ }
+}
+
+func WithKinds(kinds ...*Kind) ConfigOption {
+ return func(c *Config) {
+ for _, kind := range kinds {
+ c.Kinds[kind.Kind] = kind
+ }
+ }
+}
+
+// WithPubSubSubscriberTimeout sets a timeout used when delivering a message batch to a subscriber.
+// Default is 5s.
+func WithPubSubSubscriberTimeout(timeout time.Duration) ConfigOption {
+ return func(c *Config) {
+ c.PubSubConfig.SubscriberTimeout = timeout
+ }
+}
+
+// WithHeartbeatExpiration sets the gossip heartbeat expiration.
+func WithHeartbeatExpiration(t time.Duration) ConfigOption {
+ return func(c *Config) {
+ c.HeartbeatExpiration = t
+ }
+}
diff --git a/cluster/consensus.go b/cluster/consensus.go
new file mode 100644
index 0000000000000000000000000000000000000000..26d96d61ae7256a0a553b71b4515c43b86d2aadb
--- /dev/null
+++ b/cluster/consensus.go
@@ -0,0 +1,67 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+import (
+ "context"
+ "sync"
+
+ "github.com/google/uuid"
+)
+
+// this data structure is used to host consensus check results, the
+// contents are protected from data races as it embeds RWMutex.
+type consensusResult struct {
+ sync.Mutex
+
+ consensus bool
+ value interface{}
+}
+
+type ConsensusHandler interface {
+ GetID() string
+ TryGetConsensus(context.Context) (interface{}, bool)
+}
+
+type gossipConsensusHandler struct {
+ ID string
+ result *consensusResult
+}
+
+func NewGossipConsensusHandler() *gossipConsensusHandler {
+ handler := gossipConsensusHandler{
+ ID: uuid.New().String(),
+ result: &consensusResult{
+ consensus: false,
+ value: nil,
+ },
+ }
+
+ return &handler
+}
+
+func (hdl *gossipConsensusHandler) GetID() string { return hdl.ID }
+
+func (hdl *gossipConsensusHandler) TryGetConsensus(context.Context) (interface{}, bool) {
+ // wait until our result is available
+ hdl.result.Lock()
+ defer hdl.result.Unlock()
+
+ return hdl.result.value, hdl.result.consensus
+}
+
+func (hdl *gossipConsensusHandler) TrySetConsensus(consensus interface{}) {
+ hdl.result.Lock()
+ go func() {
+ defer hdl.result.Unlock()
+
+ hdl.result.value = consensus
+ hdl.result.consensus = true
+ }()
+}
+
+func (hdl *gossipConsensusHandler) TryResetConsensus() {
+ // this is a noop for now need to discuss the right
+ // approach for check waiting in Go as might be another
+ // way of expressing this
+}
diff --git a/cluster/consensus_check_builder.go b/cluster/consensus_check_builder.go
new file mode 100644
index 0000000000000000000000000000000000000000..523ee3bd76bf595b8ae9dca67a4d1c7517129817
--- /dev/null
+++ b/cluster/consensus_check_builder.go
@@ -0,0 +1,228 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+import (
+ "fmt"
+ "strings"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+type ConsensusCheckDefinition interface {
+ Check() *ConsensusCheck
+ AffectedKeys() map[string]struct{}
+}
+
+type consensusValue struct {
+ Key string
+ Value func(*anypb.Any) interface{}
+}
+
+type consensusMemberValue struct {
+ memberID string
+ key string
+ value uint64
+}
+
+type ConsensusCheckBuilder struct {
+ getConsensusValues []*consensusValue
+ check ConsensusChecker
+}
+
+func NewConsensusCheckBuilder(key string, getValue func(*anypb.Any) interface{}) *ConsensusCheckBuilder {
+ builder := ConsensusCheckBuilder{
+ getConsensusValues: []*consensusValue{
+ {
+ Key: key,
+ Value: getValue,
+ },
+ },
+ }
+ builder.check = builder.build()
+ return &builder
+}
+
+// Build builds a new ConsensusHandler and ConsensusCheck values and returns pointers to them
+func (ccb *ConsensusCheckBuilder) Build() (ConsensusHandler, *ConsensusCheck) {
+ handle := NewGossipConsensusHandler()
+ onConsensus := handle.TrySetConsensus
+ lostConsensus := handle.TryResetConsensus
+
+ check := func() *ConsensusCheck {
+ hasConsensus := ccb.Check()
+ hadConsensus := false
+
+ checkConsensus := func(state *GossipState, members map[string]empty) {
+ consensus, value := hasConsensus(state, members)
+ if consensus {
+ if hadConsensus {
+ return
+ }
+
+ onConsensus(value)
+ hadConsensus = true
+ } else if hadConsensus {
+ lostConsensus()
+ hadConsensus = false
+ }
+ }
+
+ consensusCheck := NewConsensusCheck(ccb.AffectedKeys(), checkConsensus)
+ return &consensusCheck
+ }
+
+ return handle, check()
+}
+
+func (ccb *ConsensusCheckBuilder) Check() ConsensusChecker { return ccb.check }
+
+func (ccb *ConsensusCheckBuilder) AffectedKeys() []string {
+ var keys []string
+ for _, value := range ccb.getConsensusValues {
+ keys = append(keys, value.Key)
+ }
+ return keys
+}
+
+func (ccb *ConsensusCheckBuilder) MapToValue(valueTuple *consensusValue) func(string, *GossipMemberState) (string, string, uint64) {
+ // REVISIT: in .NET implementation the ConsensusCheckBuilder can be of any given T type
+ // so this method returns (string, string, T) in .NET, it just feels wrong to
+ // return an interface{} from here as so far only checkers for uint64 are
+ // being used but this is not acceptable, and we shall put this implementation
+ // on par with .NET version, so maybe with new go1.18 generics or making the
+ // ConsensusCheckBuilder struct to store an additional field of type empty
+ // interface to operate with internally and then provide of a custom callback
+ // from users of the data structure to convert back and forth ¯\_(ツ)_/¯
+ key := valueTuple.Key
+ unpack := valueTuple.Value
+
+ return func(member string, state *GossipMemberState) (string, string, uint64) {
+ var value uint64
+
+ gossipKey, ok := state.Values[key]
+ if !ok {
+ value = 0
+ } else {
+ // REVISIT: the valueTuple is here supposedly to be able to convert
+ // the protobuf Any values contained by GossipMemberState
+ // into the right value, this is true in the .NET version
+ // as ConsensusCheckBuilder is defined as a generic type
+ // ConsensusCheckBuilder so the unpacker can unpack from
+ // Any into T, but we can not do that (for now) so we have
+ // to stick to unpack to the concrete uint64 type here
+ value = unpack(gossipKey.Value).(uint64)
+ }
+ return member, key, value
+ }
+}
+
+func (ccb *ConsensusCheckBuilder) build() func(*GossipState, map[string]empty) (bool, interface{}) {
+ getValidMemberStates := func(state *GossipState, ids map[string]empty, result []map[string]*GossipMemberState) {
+ for member, memberState := range state.Members {
+ if _, ok := ids[member]; ok {
+ result = append(result, map[string]*GossipMemberState{
+ member: memberState,
+ })
+ }
+ }
+ }
+
+ showLog := func(hasConsensus bool, topologyHash uint64, valueTuples []*consensusMemberValue) {
+ if plog.Level() == log.DebugLevel {
+ groups := map[string]int{}
+ for _, memberValue := range valueTuples {
+ key := fmt.Sprintf("%s:%d", memberValue.key, memberValue.value)
+ if _, ok := groups[key]; ok {
+ groups[key]++
+ } else {
+ groups[key] = 1
+ }
+ }
+
+ for k, value := range groups {
+ suffix := strings.Split(k, ":")[0]
+ if value > 1 {
+ suffix = fmt.Sprintf("%s, %d nodes", k, value)
+ }
+ plog.Debug("consensus", log.Bool("consensus", hasConsensus), log.String("values", suffix))
+ }
+ }
+ }
+
+ if len(ccb.getConsensusValues) == 1 {
+ mapToValue := ccb.MapToValue(ccb.getConsensusValues[0])
+
+ return func(state *GossipState, ids map[string]empty) (bool, interface{}) {
+ var memberStates []map[string]*GossipMemberState
+ getValidMemberStates(state, ids, memberStates)
+
+ if len(memberStates) < len(ids) { // Not all members have state...
+ return false, nil
+ }
+
+ var valueTuples []*consensusMemberValue
+ for _, memberState := range memberStates {
+ for id, state := range memberState {
+ member, key, value := mapToValue(id, state)
+ valueTuples = append(valueTuples, &consensusMemberValue{member, key, value})
+ }
+ }
+
+ hasConsensus, topologyHash := ccb.HasConsensus(valueTuples)
+ showLog(hasConsensus, topologyHash, valueTuples)
+
+ return hasConsensus, topologyHash
+ }
+ }
+
+ return func(state *GossipState, ids map[string]empty) (bool, interface{}) {
+ var memberStates []map[string]*GossipMemberState
+ getValidMemberStates(state, ids, memberStates)
+
+ if len(memberStates) < len(ids) { // Not all members have state...
+ return false, nil
+ }
+
+ var valueTuples []*consensusMemberValue
+ for _, consensusValues := range ccb.getConsensusValues {
+ mapToValue := ccb.MapToValue(consensusValues)
+ for _, memberState := range memberStates {
+ for id, state := range memberState {
+ member, key, value := mapToValue(id, state)
+ valueTuples = append(valueTuples, &consensusMemberValue{member, key, value})
+ }
+ }
+ }
+
+ hasConsensus, topologyHash := ccb.HasConsensus(valueTuples)
+ showLog(hasConsensus, topologyHash, valueTuples)
+
+ return hasConsensus, topologyHash
+ }
+}
+
+func (ccb *ConsensusCheckBuilder) HasConsensus(memberValues []*consensusMemberValue) (bool, uint64) {
+ var hasConsensus bool
+ var topologyHash uint64
+
+ if len(memberValues) == 0 {
+ return hasConsensus, topologyHash
+ }
+
+ first := memberValues[0]
+ for i, next := range memberValues {
+ if i == 0 {
+ continue
+ }
+
+ if first.value != next.value {
+ return hasConsensus, topologyHash
+ }
+ }
+
+ hasConsensus = true
+ topologyHash = first.value
+ return hasConsensus, topologyHash
+}
diff --git a/cluster/consensus_checks.go b/cluster/consensus_checks.go
new file mode 100644
index 0000000000000000000000000000000000000000..f9e30d0fd1e7a2b843bc6b960a516c950553044c
--- /dev/null
+++ b/cluster/consensus_checks.go
@@ -0,0 +1,127 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+type GossipUpdater func(*GossipState, map[string]empty)
+
+// data structure helpful to store consensus check information and behavior.
+type ConsensusCheck struct {
+ affectedKeys []string
+ check GossipUpdater
+}
+
+// creates a new ConsensusCheck value with the given data and return it back.
+func NewConsensusCheck(affectedKeys []string, check GossipUpdater) ConsensusCheck {
+ consensusCheck := ConsensusCheck{
+ affectedKeys: affectedKeys,
+ check: check,
+ }
+
+ return consensusCheck
+}
+
+// acts as a storage of pointers to ConsensusCheck stored by key.
+type ConsensusChecks struct {
+ checks map[string]*ConsensusCheck
+ affectedKeysByStateKey map[string]map[string]empty
+}
+
+// creates a new ConsensusChecks value and returns a pointer to it.
+func NewConsensusChecks() *ConsensusChecks {
+ checks := ConsensusChecks{
+ checks: make(map[string]*ConsensusCheck),
+ affectedKeysByStateKey: make(map[string]map[string]empty),
+ }
+
+ return &checks
+}
+
+// iterates over all the keys stored in the set (map[string]empty) found in
+// the given key map and populates a slice of pointers to ConsensusCheck values
+// that is returned as a set of ConsensusCheck updated by the given key.
+func (cc *ConsensusChecks) GetByUpdatedKey(key string) []*ConsensusCheck {
+ var result []*ConsensusCheck
+ if _, ok := cc.affectedKeysByStateKey[key]; !ok {
+ return result
+ }
+
+ for id := range cc.affectedKeysByStateKey[key] {
+ if ConsensusCheck, ok := cc.checks[id]; ok {
+ result = append(result, ConsensusCheck)
+ }
+ }
+
+ return result
+}
+
+// iterate over all the keys stored in the set (map[string]empty) found in
+// the given key maps and populates a slice of pointers to ConsensusCheck values
+// that is returned as a set of ConsensusCheck updated by the given keys
+// with removed duplicates on it (as it is a "set").
+func (cc *ConsensusChecks) GetByUpdatedKeys(keys []string) []*ConsensusCheck {
+ var result []*ConsensusCheck
+
+ temporaryIDs := make(map[string]empty)
+
+ for _, key := range keys {
+ if _, ok := cc.affectedKeysByStateKey[key]; !ok {
+ continue
+ }
+
+ for id := range cc.affectedKeysByStateKey[key] {
+ if ConsensusCheck, ok := cc.checks[id]; ok {
+ if _, ok := temporaryIDs[id]; !ok {
+ temporaryIDs[id] = empty{}
+
+ result = append(result, ConsensusCheck)
+ }
+ }
+ }
+ }
+
+ return result
+}
+
+// adds a new pointer to a ConsensusCheck value in the storage
+// and registers its affected by keys index.
+func (cc *ConsensusChecks) Add(id string, check *ConsensusCheck) {
+ cc.checks[id] = check
+ cc.registerAffectedKeys(id, check.affectedKeys)
+}
+
+// Remove removes the given ConsensusCheck identity from the storage and
+// removes its affected by keys index if needed after cleaning.
+func (cc *ConsensusChecks) Remove(id string) {
+ if _, ok := cc.affectedKeysByStateKey[id]; ok {
+ delete(cc.affectedKeysByStateKey, id)
+ cc.unregisterAffectedKeys(id)
+ }
+}
+
+func (cc *ConsensusChecks) registerAffectedKeys(id string, keys []string) {
+ for _, key := range keys {
+ if _, ok := cc.affectedKeysByStateKey[key]; ok {
+ cc.affectedKeysByStateKey[key][id] = empty{}
+ } else {
+ cc.affectedKeysByStateKey[key] = map[string]empty{id: {}}
+ }
+ }
+}
+
+func (cc *ConsensusChecks) unregisterAffectedKeys(id string) {
+ var keysToDelete []string
+
+ for key, internal := range cc.affectedKeysByStateKey {
+ if _, ok := internal[id]; ok {
+ delete(cc.affectedKeysByStateKey[key], id)
+
+ if len(cc.affectedKeysByStateKey[key]) == 0 {
+ keysToDelete = append(keysToDelete, key)
+ }
+ }
+ }
+
+ for _, key := range keysToDelete {
+ delete(cc.affectedKeysByStateKey, key)
+ }
+}
diff --git a/cluster/context.go b/cluster/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..50010a72347bf1c4b2affa82b7b857f70c9b836d
--- /dev/null
+++ b/cluster/context.go
@@ -0,0 +1,8 @@
+package cluster
+
+import "time"
+
+// Context is an interface any cluster context needs to implement
+type Context interface {
+ Request(identity string, kind string, message interface{}, timeout ...time.Duration) (interface{}, error)
+}
diff --git a/cluster/default_context.go b/cluster/default_context.go
new file mode 100644
index 0000000000000000000000000000000000000000..d8e3af452cfb138f90ea2019cdd9b5fe8071a72d
--- /dev/null
+++ b/cluster/default_context.go
@@ -0,0 +1,120 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package cluster
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+// Defines a type to provide DefaultContext configurations / implementations.
+type ContextProducer func(*Cluster) Context
+
+// Defines a default cluster context hashBytes structure.
+type DefaultContext struct {
+ cluster *Cluster
+}
+
+var _ Context = (*DefaultContext)(nil)
+
+// Creates a new DefaultContext value and returns
+// a pointer to its memory address as a Context.
+func newDefaultClusterContext(cluster *Cluster) Context {
+ clusterContext := DefaultContext{
+ cluster: cluster,
+ }
+
+ return &clusterContext
+}
+
+func (dcc *DefaultContext) Request(identity, kind string, message interface{}, timeout ...time.Duration) (interface{}, error) {
+ var err error
+
+ var resp interface{}
+
+ var counter int
+
+ // get the configuration from the composed Cluster value
+ cfg := dcc.cluster.Config.ToClusterContextConfig()
+
+ start := time.Now()
+
+ plog.Debug(fmt.Sprintf("Requesting %s:%s Message %#v", identity, kind, message))
+
+ // crate a new Timeout Context
+ ttl := cfg.ActorRequestTimeout
+ if len(timeout) > 0 {
+ ttl = timeout[0]
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), ttl)
+ defer cancel()
+
+ _context := dcc.cluster.ActorSystem.Root
+selectloop:
+ for {
+ select {
+ case <-ctx.Done():
+ // TODO: handler throttling and messaging here
+ err = fmt.Errorf("request failed: %w", ctx.Err())
+
+ break selectloop
+ default:
+ pid := dcc.getCachedPid(identity, kind)
+ if pid == nil {
+ plog.Debug(fmt.Sprintf("Requesting %s:%s did not get PID from IdentityLookup", identity, kind))
+ counter = cfg.RetryAction(counter)
+
+ continue
+ }
+
+ resp, err = _context.RequestFuture(pid, message, ttl).Result()
+ if err != nil {
+ plog.Error("cluster.RequestFuture failed", log.Error(err), log.PID("pid", pid))
+ switch err {
+ case actor.ErrTimeout, remote.ErrTimeout, actor.ErrDeadLetter, remote.ErrDeadLetter:
+ counter = cfg.RetryAction(counter)
+ dcc.cluster.PidCache.Remove(identity, kind)
+ err = nil // reset our error variable as we can succeed still
+
+ continue
+ default:
+ break selectloop
+ }
+ }
+
+ // TODO: add metrics to increment retries
+ }
+ }
+
+ totalTime := time.Since(start)
+ // TODO: add metrics ot set histogram for total request time
+
+ if contextError := ctx.Err(); contextError != nil && cfg.requestLogThrottle() == actor.Open {
+ // context timeout exceeded, report and return
+ plog.Warn(fmt.Sprintf("Request retried but failed for %s:%s, elapsed %v", identity, kind, totalTime))
+ }
+
+ return resp, err
+}
+
+// gets the cached PID for the given identity
+// it can return nil if none is found.
+func (dcc *DefaultContext) getCachedPid(identity, kind string) *actor.PID {
+ pid, _ := dcc.cluster.PidCache.Get(identity, kind)
+
+ return pid
+}
+
+// default retry action, it just sleeps incrementally.
+func defaultRetryAction(i int) int {
+ i++
+ time.Sleep(time.Duration(i * i * 50))
+
+ return i
+}
diff --git a/cluster/gossip.go b/cluster/gossip.go
new file mode 100644
index 0000000000000000000000000000000000000000..bac7762b2cd31623a6f4783b5b5e990d8dbdb31a
--- /dev/null
+++ b/cluster/gossip.go
@@ -0,0 +1,41 @@
+// Copyright (C) 2015-2022 Asynkton AB All rights reserved
+
+package cluster
+
+import (
+ "google.golang.org/protobuf/proto"
+)
+
+// customary type that defines a states sender callback.
+type LocalStateSender func(memberStateDelta *MemberStateDelta, member *Member)
+
+// This interface must be implemented by any value that.
+// wants to be used as a gossip state storage
+type GossipStateStorer interface {
+ GetState(key string) map[string]*GossipKeyValue
+ SetState(key string, value proto.Message)
+}
+
+// This interface must be implemented by any value that
+// wants to add or remove consensus checkers
+type GossipConsensusChecker interface {
+ AddConsensusCheck(id string, check *ConsensusCheck)
+ RemoveConsensusCheck(id string)
+}
+
+// This interface must be implemented by any value that
+// wants to react to cluster topology events
+type GossipCore interface {
+ UpdateClusterTopology(topology *ClusterTopology)
+ ReceiveState(remoteState *GossipState) []*GossipUpdate
+ SendState(sendStateToMember LocalStateSender)
+ GetMemberStateDelta(targetMemberID string) *MemberStateDelta
+}
+
+// The Gossip interface must be implemented by any value
+// that pretends to participate with-in the Gossip protocol
+type Gossip interface {
+ GossipStateStorer
+ GossipConsensusChecker
+ GossipCore
+}
diff --git a/cluster/gossip.pb.go b/cluster/gossip.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..d1cdae2939e0aa381f5327e9b8cca5cf86de53dc
--- /dev/null
+++ b/cluster/gossip.pb.go
@@ -0,0 +1,615 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.26.0
+// protoc v3.20.2
+// source: gossip.proto
+
+package cluster
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ anypb "google.golang.org/protobuf/types/known/anypb"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GossipRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MemberId string `protobuf:"bytes,2,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"`
+ State *GossipState `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
+}
+
+func (x *GossipRequest) Reset() {
+ *x = GossipRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipRequest) ProtoMessage() {}
+
+func (x *GossipRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipRequest.ProtoReflect.Descriptor instead.
+func (*GossipRequest) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GossipRequest) GetMemberId() string {
+ if x != nil {
+ return x.MemberId
+ }
+ return ""
+}
+
+func (x *GossipRequest) GetState() *GossipState {
+ if x != nil {
+ return x.State
+ }
+ return nil
+}
+
+// Ack a gossip request
+type GossipResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ State *GossipState `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
+}
+
+func (x *GossipResponse) Reset() {
+ *x = GossipResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipResponse) ProtoMessage() {}
+
+func (x *GossipResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipResponse.ProtoReflect.Descriptor instead.
+func (*GossipResponse) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GossipResponse) GetState() *GossipState {
+ if x != nil {
+ return x.State
+ }
+ return nil
+}
+
+// two GossipState objects can be merged
+// key + member_id gets it's own entry, if collision, highest version is selected
+type GossipState struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Members map[string]*GossipState_GossipMemberState `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *GossipState) Reset() {
+ *x = GossipState{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipState) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipState) ProtoMessage() {}
+
+func (x *GossipState) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipState.ProtoReflect.Descriptor instead.
+func (*GossipState) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GossipState) GetMembers() map[string]*GossipState_GossipMemberState {
+ if x != nil {
+ return x.Members
+ }
+ return nil
+}
+
+// a known key might be heartbeat. if we locally tag each entry with a local timestamp
+// this means that we can measure if we have not received a new heartbeat from one member in some time
+// even if we don't know the exact time the heartbeat was issued, due to clock differences.
+// we still know when _we_ as in this node, got this data.
+// and we can measure time from then til now.
+//
+// if we got a hear-beat from another node, and X seconds pass, we can assume it to be dead
+type GossipKeyValue struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SequenceNumber int64 `protobuf:"varint,2,opt,name=sequence_number,json=sequenceNumber,proto3" json:"sequence_number,omitempty"` //version is local to the owner member
+ Value *anypb.Any `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` //value is any format
+ LocalTimestampUnixMilliseconds int64 `protobuf:"varint,5,opt,name=local_timestamp_unix_milliseconds,json=localTimestampUnixMilliseconds,proto3" json:"local_timestamp_unix_milliseconds,omitempty"`
+}
+
+func (x *GossipKeyValue) Reset() {
+ *x = GossipKeyValue{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipKeyValue) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipKeyValue) ProtoMessage() {}
+
+func (x *GossipKeyValue) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipKeyValue.ProtoReflect.Descriptor instead.
+func (*GossipKeyValue) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GossipKeyValue) GetSequenceNumber() int64 {
+ if x != nil {
+ return x.SequenceNumber
+ }
+ return 0
+}
+
+func (x *GossipKeyValue) GetValue() *anypb.Any {
+ if x != nil {
+ return x.Value
+ }
+ return nil
+}
+
+func (x *GossipKeyValue) GetLocalTimestampUnixMilliseconds() int64 {
+ if x != nil {
+ return x.LocalTimestampUnixMilliseconds
+ }
+ return 0
+}
+
+// represents a value that can be sent in form of a delta change
+// instead of a full value replace
+type GossipDeltaValue struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Entries []*GossipDeltaValue_GossipDeltaEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
+}
+
+func (x *GossipDeltaValue) Reset() {
+ *x = GossipDeltaValue{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipDeltaValue) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipDeltaValue) ProtoMessage() {}
+
+func (x *GossipDeltaValue) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipDeltaValue.ProtoReflect.Descriptor instead.
+func (*GossipDeltaValue) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *GossipDeltaValue) GetEntries() []*GossipDeltaValue_GossipDeltaEntry {
+ if x != nil {
+ return x.Entries
+ }
+ return nil
+}
+
+type GossipState_GossipMemberState struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Values map[string]*GossipKeyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *GossipState_GossipMemberState) Reset() {
+ *x = GossipState_GossipMemberState{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipState_GossipMemberState) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipState_GossipMemberState) ProtoMessage() {}
+
+func (x *GossipState_GossipMemberState) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipState_GossipMemberState.ProtoReflect.Descriptor instead.
+func (*GossipState_GossipMemberState) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{2, 0}
+}
+
+func (x *GossipState_GossipMemberState) GetValues() map[string]*GossipKeyValue {
+ if x != nil {
+ return x.Values
+ }
+ return nil
+}
+
+// these are the entries of a delta value
+// this can be seen as an array with data, where each element in the array is tagged with a sequence number
+type GossipDeltaValue_GossipDeltaEntry struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SequenceNumber int64 `protobuf:"varint,1,opt,name=sequence_number,json=sequenceNumber,proto3" json:"sequence_number,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *GossipDeltaValue_GossipDeltaEntry) Reset() {
+ *x = GossipDeltaValue_GossipDeltaEntry{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_gossip_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GossipDeltaValue_GossipDeltaEntry) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GossipDeltaValue_GossipDeltaEntry) ProtoMessage() {}
+
+func (x *GossipDeltaValue_GossipDeltaEntry) ProtoReflect() protoreflect.Message {
+ mi := &file_gossip_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GossipDeltaValue_GossipDeltaEntry.ProtoReflect.Descriptor instead.
+func (*GossipDeltaValue_GossipDeltaEntry) Descriptor() ([]byte, []int) {
+ return file_gossip_proto_rawDescGZIP(), []int{4, 0}
+}
+
+func (x *GossipDeltaValue_GossipDeltaEntry) GetSequenceNumber() int64 {
+ if x != nil {
+ return x.SequenceNumber
+ }
+ return 0
+}
+
+func (x *GossipDeltaValue_GossipDeltaEntry) GetData() []byte {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+var File_gossip_proto protoreflect.FileDescriptor
+
+var file_gossip_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x22, 0x58, 0x0a, 0x0d, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64,
+ 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64,
+ 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x14, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70,
+ 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3c, 0x0a, 0x0e,
+ 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a,
+ 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0xe4, 0x02, 0x0a, 0x0b, 0x47,
+ 0x6f, 0x73, 0x73, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x07, 0x6d, 0x65,
+ 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74,
+ 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07,
+ 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x1a, 0xb3, 0x01, 0x0a, 0x11, 0x47, 0x6f, 0x73, 0x73,
+ 0x69, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4a, 0x0a,
+ 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72,
+ 0x53, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72,
+ 0x79, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x0b, 0x56, 0x61, 0x6c,
+ 0x75, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61,
+ 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6c, 0x75, 0x73,
+ 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c,
+ 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x62, 0x0a,
+ 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
+ 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+ 0x3c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26,
+ 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x53,
+ 0x74, 0x61, 0x74, 0x65, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65,
+ 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
+ 0x01, 0x22, 0xb0, 0x01, 0x0a, 0x0e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4b, 0x65, 0x79, 0x56,
+ 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65,
+ 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73,
+ 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a,
+ 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41,
+ 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x21, 0x6c, 0x6f, 0x63,
+ 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x75, 0x6e, 0x69,
+ 0x78, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05,
+ 0x20, 0x01, 0x28, 0x03, 0x52, 0x1e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x73,
+ 0x74, 0x61, 0x6d, 0x70, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x65, 0x63,
+ 0x6f, 0x6e, 0x64, 0x73, 0x22, 0xa9, 0x01, 0x0a, 0x10, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x44,
+ 0x65, 0x6c, 0x74, 0x61, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x65, 0x6e, 0x74,
+ 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6c, 0x75,
+ 0x73, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x74, 0x61,
+ 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x74,
+ 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a,
+ 0x4f, 0x0a, 0x10, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x45, 0x6e,
+ 0x74, 0x72, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f,
+ 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x65,
+ 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04,
+ 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
+ 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63,
+ 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62, 0x06,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_gossip_proto_rawDescOnce sync.Once
+ file_gossip_proto_rawDescData = file_gossip_proto_rawDesc
+)
+
+func file_gossip_proto_rawDescGZIP() []byte {
+ file_gossip_proto_rawDescOnce.Do(func() {
+ file_gossip_proto_rawDescData = protoimpl.X.CompressGZIP(file_gossip_proto_rawDescData)
+ })
+ return file_gossip_proto_rawDescData
+}
+
+var file_gossip_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
+var file_gossip_proto_goTypes = []interface{}{
+ (*GossipRequest)(nil), // 0: cluster.GossipRequest
+ (*GossipResponse)(nil), // 1: cluster.GossipResponse
+ (*GossipState)(nil), // 2: cluster.GossipState
+ (*GossipKeyValue)(nil), // 3: cluster.GossipKeyValue
+ (*GossipDeltaValue)(nil), // 4: cluster.GossipDeltaValue
+ (*GossipState_GossipMemberState)(nil), // 5: cluster.GossipState.GossipMemberState
+ nil, // 6: cluster.GossipState.MembersEntry
+ nil, // 7: cluster.GossipState.GossipMemberState.ValuesEntry
+ (*GossipDeltaValue_GossipDeltaEntry)(nil), // 8: cluster.GossipDeltaValue.GossipDeltaEntry
+ (*anypb.Any)(nil), // 9: google.protobuf.Any
+}
+var file_gossip_proto_depIdxs = []int32{
+ 2, // 0: cluster.GossipRequest.state:type_name -> cluster.GossipState
+ 2, // 1: cluster.GossipResponse.state:type_name -> cluster.GossipState
+ 6, // 2: cluster.GossipState.members:type_name -> cluster.GossipState.MembersEntry
+ 9, // 3: cluster.GossipKeyValue.value:type_name -> google.protobuf.Any
+ 8, // 4: cluster.GossipDeltaValue.entries:type_name -> cluster.GossipDeltaValue.GossipDeltaEntry
+ 7, // 5: cluster.GossipState.GossipMemberState.values:type_name -> cluster.GossipState.GossipMemberState.ValuesEntry
+ 5, // 6: cluster.GossipState.MembersEntry.value:type_name -> cluster.GossipState.GossipMemberState
+ 3, // 7: cluster.GossipState.GossipMemberState.ValuesEntry.value:type_name -> cluster.GossipKeyValue
+ 8, // [8:8] is the sub-list for method output_type
+ 8, // [8:8] is the sub-list for method input_type
+ 8, // [8:8] is the sub-list for extension type_name
+ 8, // [8:8] is the sub-list for extension extendee
+ 0, // [0:8] is the sub-list for field type_name
+}
+
+func init() { file_gossip_proto_init() }
+func file_gossip_proto_init() {
+ if File_gossip_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_gossip_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipState); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipKeyValue); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipDeltaValue); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipState_GossipMemberState); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_gossip_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GossipDeltaValue_GossipDeltaEntry); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_gossip_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 9,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_gossip_proto_goTypes,
+ DependencyIndexes: file_gossip_proto_depIdxs,
+ MessageInfos: file_gossip_proto_msgTypes,
+ }.Build()
+ File_gossip_proto = out.File
+ file_gossip_proto_rawDesc = nil
+ file_gossip_proto_goTypes = nil
+ file_gossip_proto_depIdxs = nil
+}
diff --git a/cluster/gossip.proto b/cluster/gossip.proto
new file mode 100644
index 0000000000000000000000000000000000000000..21ae4323603f59f3b3ecafc22fbdc8a9a1d9b121
--- /dev/null
+++ b/cluster/gossip.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+package cluster;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster";
+import "google/protobuf/any.proto";
+
+
+message GossipRequest {
+ string member_id = 2;
+ GossipState state = 1;
+}
+
+//Ack a gossip request
+message GossipResponse {
+ GossipState state = 1;
+}
+
+//two GossipState objects can be merged
+//key + member_id gets it's own entry, if collision, highest version is selected
+message GossipState {
+ message GossipMemberState {
+ map values = 1;
+ }
+
+ map members = 1;
+}
+
+
+
+//a known key might be heartbeat. if we locally tag each entry with a local timestamp
+//this means that we can measure if we have not received a new heartbeat from one member in some time
+//even if we don't know the exact time the heartbeat was issued, due to clock differences.
+//we still know when _we_ as in this node, got this data.
+//and we can measure time from then til now.
+//
+//if we got a hear-beat from another node, and X seconds pass, we can assume it to be dead
+message GossipKeyValue {
+ int64 sequence_number = 2; //version is local to the owner member
+ google.protobuf.Any value = 4; //value is any format
+ int64 local_timestamp_unix_milliseconds = 5;
+}
+
+//represents a value that can be sent in form of a delta change
+//instead of a full value replace
+message GossipDeltaValue
+{
+ //these are the entries of a delta value
+ //this can be seen as an array with data, where each element in the array is tagged with a sequence number
+ message GossipDeltaEntry
+ {
+ int64 sequence_number = 1;
+ bytes data = 2;
+ }
+
+ repeated GossipDeltaEntry entries = 1;
+}
\ No newline at end of file
diff --git a/cluster/gossip_actor.go b/cluster/gossip_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..5df9df15ba7c4992fba2eb156f2279609674e960
--- /dev/null
+++ b/cluster/gossip_actor.go
@@ -0,0 +1,202 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/asynkron/gofun/set"
+)
+
+// convenience customary type to represent an empty value
+// that takes no space in memory.
+type empty struct{}
+
+// Actor used to send gossip messages around
+type GossipActor struct {
+ gossipRequestTimeout time.Duration
+ gossip Gossip
+
+ /// Message throttler
+ throttler actor.ShouldThrottle
+}
+
+// Creates a new GossipActor and returns a pointer to its location in the heap
+func NewGossipActor(requestTimeout time.Duration, myID string, getBlockedMembers func() set.Set[string], fanOut int, maxSend int) *GossipActor {
+ informer := newInformer(myID, getBlockedMembers, fanOut, maxSend)
+ gossipActor := GossipActor{
+ gossipRequestTimeout: requestTimeout,
+ gossip: informer,
+ }
+ gossipActor.throttler = actor.NewThrottle(3, 60*time.Second, gossipActor.throttledLog)
+
+ return &gossipActor
+}
+
+// Receive method.
+func (ga *GossipActor) Receive(ctx actor.Context) {
+ switch r := ctx.Message().(type) {
+ case *actor.Started:
+ // pass
+ case *SetGossipStateKey:
+ ga.onSetGossipStateKey(r, ctx)
+ case *GetGossipStateRequest:
+ ga.onGetGossipStateKey(r, ctx)
+ case *GossipRequest:
+ ga.onGossipRequest(r, ctx)
+ case *SendGossipStateRequest:
+ ga.onSendGossipState(ctx)
+ case *AddConsensusCheck:
+ ga.onAddConsensusCheck(r)
+ case *RemoveConsensusCheck:
+ ga.onRemoveConsensusCheck(r)
+ case *ClusterTopology:
+ ga.onClusterTopology(r)
+ case *GossipResponse:
+ plog.Error("GossipResponse should not be received by GossipActor") // it should be a response to a request
+ default:
+ plog.Warn("Gossip received unknown message request", log.Message(r), log.TypeOf("msg_type", r))
+ }
+}
+
+func (ga *GossipActor) onClusterTopology(topology *ClusterTopology) {
+ ga.gossip.UpdateClusterTopology(topology)
+}
+
+func (ga *GossipActor) onAddConsensusCheck(r *AddConsensusCheck) {
+ ga.gossip.AddConsensusCheck(r.ID, r.Check)
+}
+
+func (ga *GossipActor) onRemoveConsensusCheck(r *RemoveConsensusCheck) {
+ ga.gossip.RemoveConsensusCheck(r.ID)
+}
+
+func (ga *GossipActor) onGetGossipStateKey(r *GetGossipStateRequest, ctx actor.Context) {
+ state := ga.gossip.GetState(r.Key)
+ res := NewGetGossipStateResponse(state)
+ ctx.Respond(&res)
+}
+
+func (ga *GossipActor) onGossipRequest(r *GossipRequest, ctx actor.Context) {
+ if ga.throttler() == actor.Open {
+ plog.Debug("OnGossipRequest", log.PID("sender", ctx.Sender()))
+ }
+ ga.ReceiveState(r.State, ctx)
+
+ if !GetCluster(ctx.ActorSystem()).MemberList.ContainsMemberID(r.MemberId) {
+ plog.Warn("Got gossip request from unknown member", log.String("MemberId", r.MemberId))
+
+ // nothing to send, do not provide sender or state payload
+ // ctx.Respond(&GossipResponse{State: &GossipState{Members: make(map[string]*GossipState_GossipMemberState)}})
+ ctx.Respond(&GossipResponse{})
+
+ return
+ }
+
+ memberState := ga.gossip.GetMemberStateDelta(r.MemberId)
+ if !memberState.HasState {
+ plog.Warn("Got gossip request from member, but no state was found", log.String("MemberId", r.MemberId))
+
+ // nothing to send, do not provide sender or state payload
+ ctx.Respond(&GossipResponse{})
+
+ return
+ }
+
+ ctx.Respond(&GossipResponse{})
+ return
+
+ // turn off acking for now
+
+ //msg := GossipResponse{
+ // State: memberState.State,
+ //}
+ //future := ctx.RequestFuture(ctx.Sender(), &msg, GetCluster(ctx.ActorSystem()).Config.GossipRequestTimeout)
+ //
+ //ctx.ReenterAfter(future, func(res interface{}, err error) {
+ // if err != nil {
+ // plog.Warn("onGossipRequest failed", log.String("MemberId", r.MemberId), log.Error(err))
+ // return
+ // }
+ //
+ // if _, ok := res.(*GossipResponseAck); ok {
+ // memberState.CommitOffsets()
+ // return
+ // }
+ //
+ // m, ok := res.(proto.Message)
+ // if !ok {
+ // plog.Warn("onGossipRequest failed", log.String("MemberId", r.MemberId), log.Error(err))
+ // return
+ // }
+ // n := string(proto.MessageName(m).Name())
+ //
+ // plog.Error("onGossipRequest received unknown response message", log.String("type", n), log.Message(r))
+ //})
+}
+
+func (ga *GossipActor) onSetGossipStateKey(r *SetGossipStateKey, ctx actor.Context) {
+ key, message := r.Key, r.Value
+ ga.gossip.SetState(key, message)
+
+ if ctx.Sender() != nil {
+ ctx.Respond(&SetGossipStateResponse{})
+ }
+}
+
+func (ga *GossipActor) onSendGossipState(ctx actor.Context) {
+ ga.gossip.SendState(func(memberState *MemberStateDelta, member *Member) {
+ ga.sendGossipForMember(member, memberState, ctx)
+ })
+ ctx.Respond(&SendGossipStateResponse{})
+}
+
+func (ga *GossipActor) ReceiveState(remoteState *GossipState, ctx actor.Context) {
+ // stream our updates
+ updates := ga.gossip.ReceiveState(remoteState)
+ for _, update := range updates {
+ ctx.ActorSystem().EventStream.Publish(update)
+ }
+}
+
+func (ga *GossipActor) sendGossipForMember(member *Member, memberStateDelta *MemberStateDelta, ctx actor.Context) {
+ pid := actor.NewPID(member.Address(), DefaultGossipActorName)
+ if ga.throttler() == actor.Open {
+ plog.Debug("Sending GossipRequest", log.String("MemberId", member.Id))
+ }
+
+ // a short timeout is massively important, we cannot afford hanging around waiting
+ // for timeout, blocking other gossips from getting through
+
+ msg := GossipRequest{
+ MemberId: member.Id,
+ State: memberStateDelta.State,
+ }
+ future := ctx.RequestFuture(pid, &msg, ga.gossipRequestTimeout)
+
+ ctx.ReenterAfter(future, func(res interface{}, err error) {
+ if err != nil {
+ plog.Warn("sendGossipForMember failed", log.String("MemberId", member.Id), log.Error(err))
+ return
+ }
+
+ resp, ok := res.(*GossipResponse)
+ if !ok {
+ plog.Error("sendGossipForMember received unknown response message", log.TypeOf("messageType", res), log.Message(resp))
+
+ return
+ }
+
+ memberStateDelta.CommitOffsets()
+
+ if resp.State != nil {
+ ga.ReceiveState(resp.State, ctx)
+ }
+ })
+}
+
+func (ga *GossipActor) throttledLog(counter int32) {
+ plog.Debug("[Gossip] Sending GossipRequest", log.Int("throttled", int(counter)))
+}
diff --git a/cluster/gossip_state_management.go b/cluster/gossip_state_management.go
new file mode 100644
index 0000000000000000000000000000000000000000..536036a3b4e7d0de1add72c91a1b5239f01904f3
--- /dev/null
+++ b/cluster/gossip_state_management.go
@@ -0,0 +1,122 @@
+package cluster
+
+import (
+ "time"
+
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+// convenience type alias
+type GossipMemberState = GossipState_GossipMemberState
+
+func ensureEntryExists(memberState *GossipMemberState, key string) *GossipKeyValue {
+ value, ok := memberState.Values[key]
+ if ok {
+ return value
+ }
+
+ value = &GossipKeyValue{}
+ memberState.Values[key] = value
+
+ return value
+}
+
+// returns back the GossipMemberState registered in the given GossipState
+// under the given memberID key, if the key doesn't exists yet it is created
+func ensureMemberStateExists(state *GossipState, memberID string) *GossipMemberState {
+ memberState, ok := state.Members[memberID]
+ if ok {
+ return memberState
+ }
+
+ memberState = &GossipMemberState{Values: make(map[string]*GossipKeyValue)}
+ state.Members[memberID] = memberState
+
+ return memberState
+}
+
+// sets the given key with the given value in the given gossip state and returns sequenceNo + 1
+func setKey(state *GossipState, key string, value proto.Message, memberID string, sequenceNo int64) int64 {
+ // if entry does not exists, add it
+ memberState := ensureMemberStateExists(state, memberID)
+ entry := ensureEntryExists(memberState, key)
+ entry.LocalTimestampUnixMilliseconds = time.Now().UnixMilli()
+
+ sequenceNo++
+ entry.SequenceNumber = sequenceNo
+
+ a, _ := anypb.New(value)
+ entry.Value = a
+
+ return sequenceNo
+}
+
+// merges the local and the incoming remote states into a new states slice and return it
+func mergeState(localState *GossipState, remoteState *GossipState) ([]*GossipUpdate, *GossipState, map[string]empty) {
+ // make a copy of the localState (we do not want to modify localState just yet)
+ mergedState := &GossipState{Members: make(map[string]*GossipState_GossipMemberState)}
+ for id, member := range localState.Members {
+ mergedState.Members[id] = member
+ }
+
+ var updates []*GossipUpdate
+ updatedKeys := make(map[string]empty)
+
+ for memberID, remoteMemberState := range remoteState.Members {
+ if _, ok := mergedState.Members[memberID]; !ok {
+ mergedState.Members[memberID] = remoteMemberState
+ for key, entry := range remoteMemberState.Values {
+ update := GossipUpdate{
+ MemberID: memberID,
+ Key: key,
+ Value: entry.Value,
+ SeqNumber: entry.SequenceNumber,
+ }
+ updates = append(updates, &update)
+ entry.LocalTimestampUnixMilliseconds = time.Now().UnixMilli()
+ updatedKeys[key] = empty{}
+ }
+ continue
+ }
+
+ // this entry exists in both mergedState and remoteState, we should merge them
+ newMemberState := mergedState.Members[memberID]
+ for key, remoteValue := range remoteMemberState.Values {
+ // this entry does not exist in newMemberState, just copy all of it
+ if _, ok := newMemberState.Values[key]; !ok {
+ newMemberState.Values[key] = remoteValue
+ update := GossipUpdate{
+ MemberID: memberID,
+ Key: key,
+ Value: remoteValue.Value,
+ SeqNumber: remoteValue.SequenceNumber,
+ }
+ updates = append(updates, &update)
+ remoteValue.LocalTimestampUnixMilliseconds = time.Now().UnixMilli()
+ updatedKeys[key] = empty{}
+ continue
+ }
+
+ newValue := newMemberState.Values[key]
+
+ // remote value is older, ignore
+ if remoteValue.SequenceNumber <= newValue.SequenceNumber {
+ continue
+ }
+
+ // just replace the existing value
+ newMemberState.Values[key] = remoteValue
+ update := GossipUpdate{
+ MemberID: memberID,
+ Key: key,
+ Value: remoteValue.Value,
+ SeqNumber: remoteValue.SequenceNumber,
+ }
+ updates = append(updates, &update)
+ remoteValue.LocalTimestampUnixMilliseconds = time.Now().UnixMilli()
+ updatedKeys[key] = empty{}
+ }
+ }
+ return updates, mergedState, updatedKeys
+}
diff --git a/cluster/gossiper.go b/cluster/gossiper.go
new file mode 100644
index 0000000000000000000000000000000000000000..e6b83e10e5516be799edbce40bb58db461d1e126
--- /dev/null
+++ b/cluster/gossiper.go
@@ -0,0 +1,301 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/remote"
+
+ "github.com/asynkron/gofun/set"
+ "google.golang.org/protobuf/proto"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+const DefaultGossipActorName string = "gossip"
+
+// GossipUpdate Used to update gossip data when a ClusterTopology event occurs
+type GossipUpdate struct {
+ MemberID, Key string
+ Value *anypb.Any
+ SeqNumber int64
+}
+
+// ConsensusChecker Customary type used to provide consensus check callbacks of any type
+// note: this is equivalent to (for future go v1.18):
+//
+// type ConsensusChecker[T] func(GossipState, map[string]empty) (bool, T)
+type ConsensusChecker func(*GossipState, map[string]empty) (bool, interface{})
+
+// The Gossiper data structure manages Gossip
+type Gossiper struct {
+ // The Gossiper Actor Name, defaults to "gossip"
+ GossipActorName string
+
+ // The Gossiper Cluster
+ cluster *Cluster
+
+ // The actor PID
+ pid *actor.PID
+
+ // Channel use to stop the gossip loop
+ close chan struct{}
+
+ // Message throttler
+ throttler actor.ShouldThrottle
+}
+
+// Creates a new Gossiper value and return it back
+func newGossiper(cl *Cluster, opts ...Option) (*Gossiper, error) {
+ // create a new Gossiper value
+ gossiper := &Gossiper{
+ GossipActorName: DefaultGossipActorName,
+ cluster: cl,
+ close: make(chan struct{}),
+ }
+
+ // apply any given options
+ for _, opt := range opts {
+ opt(gossiper)
+ }
+
+ return gossiper, nil
+}
+
+func (g *Gossiper) GetState(key string) (map[string]*GossipKeyValue, error) {
+ plog.Debug(fmt.Sprintf("Gossiper getting state from %s", g.pid))
+
+ msg := NewGetGossipStateRequest(key)
+ timeout := g.cluster.Config.TimeoutTime
+ r, err := g.cluster.ActorSystem.Root.RequestFuture(g.pid, &msg, timeout).Result()
+ if err != nil {
+ switch err {
+ case actor.ErrTimeout:
+ plog.Error("Could not get a response from GossipActor: request timeout", log.Error(err), log.String("remote", g.pid.String()))
+ return nil, err
+ case actor.ErrDeadLetter:
+ plog.Error("remote no longer exists", log.Error(err), log.String("remote", g.pid.String()))
+ return nil, err
+ default:
+ plog.Error("Could not get a response from GossipActor", log.Error(err), log.String("remote", g.pid.String()))
+ return nil, err
+ }
+ }
+
+ // try to cast the response to GetGossipStateResponse concrete value
+ response, ok := r.(*GetGossipStateResponse)
+ if !ok {
+ err := fmt.Errorf("could not promote %T interface to GetGossipStateResponse", r)
+ plog.Error("Could not get a response from GossipActor", log.Error(err), log.String("remote", g.pid.String()))
+ return nil, err
+ }
+
+ return response.State, nil
+}
+
+// SetState Sends fire and forget message to update member state
+func (g *Gossiper) SetState(key string, value proto.Message) {
+ if g.throttler() == actor.Open {
+ plog.Debug(fmt.Sprintf("Gossiper setting state %s to %s", key, g.pid))
+ }
+
+ if g.pid == nil {
+ return
+ }
+
+ msg := NewGossipStateKey(key, value)
+ g.cluster.ActorSystem.Root.Send(g.pid, &msg)
+}
+
+// SetStateRequest Sends a Request (that blocks) to update member state
+func (g *Gossiper) SetStateRequest(key string, value proto.Message) error {
+ if g.throttler() == actor.Open {
+ plog.Debug(fmt.Sprintf("Gossiper setting state %s to %s", key, g.pid))
+ }
+
+ if g.pid == nil {
+ return errors.New("gossiper Actor PID is nil")
+ }
+
+ msg := NewGossipStateKey(key, value)
+ r, err := g.cluster.ActorSystem.Root.RequestFuture(g.pid, &msg, g.cluster.Config.TimeoutTime).Result()
+ if err != nil {
+ if err == actor.ErrTimeout {
+ plog.Error("Could not get a response from Gossiper Actor: request timeout", log.String("remote", g.pid.String()))
+ return err
+ }
+ plog.Error("Could not get a response from Gossiper Actor", log.Error(err), log.String("remote", g.pid.String()))
+ return err
+ }
+
+ // try to cast the response to SetGossipStateResponse concrete value
+ _, ok := r.(*SetGossipStateResponse)
+ if !ok {
+ err := fmt.Errorf("could not promote %T interface to SetGossipStateResponse", r)
+ plog.Error("Could not get a response from Gossip Actor", log.Error(err), log.String("remote", g.pid.String()))
+ return err
+ }
+ return nil
+}
+
+func (g *Gossiper) SendState() {
+ if g.pid == nil {
+ return
+ }
+
+ r, err := g.cluster.ActorSystem.Root.RequestFuture(g.pid, &SendGossipStateRequest{}, 5*time.Second).Result()
+ if err != nil {
+ plog.Warn("Gossip could not send gossip request", log.PID("PID", g.pid), log.Error(err))
+ return
+ }
+
+ if _, ok := r.(*SendGossipStateResponse); !ok {
+ plog.Error("Gossip SendState received unknown response", log.Message(r))
+ }
+}
+
+// RegisterConsensusCheck Builds a consensus handler and a consensus checker, send the checker to the
+// Gossip actor and returns the handler back to the caller
+func (g *Gossiper) RegisterConsensusCheck(key string, getValue func(*anypb.Any) interface{}) ConsensusHandler {
+ definition := NewConsensusCheckBuilder(key, getValue)
+ consensusHandle, check := definition.Build()
+ request := NewAddConsensusCheck(consensusHandle.GetID(), check)
+ g.cluster.ActorSystem.Root.Send(g.pid, &request)
+ return consensusHandle
+}
+
+func (g *Gossiper) StartGossiping() error {
+ var err error
+ g.pid, err = g.cluster.ActorSystem.Root.SpawnNamed(actor.PropsFromProducer(func() actor.Actor {
+ return NewGossipActor(
+ g.cluster.Config.GossipRequestTimeout,
+ g.cluster.ActorSystem.ID,
+ func() set.Set[string] {
+ return g.cluster.GetBlockedMembers()
+ },
+ g.cluster.Config.GossipFanOut,
+ g.cluster.Config.GossipMaxSend,
+ )
+ }), g.GossipActorName)
+
+ if err != nil {
+ plog.Error("Failed to start gossip actor", log.Error(err))
+ return err
+ }
+
+ g.cluster.ActorSystem.EventStream.Subscribe(func(evt interface{}) {
+ if topology, ok := evt.(*ClusterTopology); ok {
+ g.cluster.ActorSystem.Root.Send(g.pid, topology)
+ }
+ })
+ plog.Info("Started Cluster Gossip")
+ g.throttler = actor.NewThrottle(3, 60*time.Second, g.throttledLog)
+ go g.gossipLoop()
+
+ return nil
+}
+
+func (g *Gossiper) Shutdown() {
+ if g.pid == nil {
+ return
+ }
+
+ plog.Info("Shutting down gossip")
+
+ close(g.close)
+
+ err := g.cluster.ActorSystem.Root.StopFuture(g.pid).Wait()
+ if err != nil {
+ plog.Error("failed to stop gossip actor", log.Error(err))
+ }
+
+ plog.Info("Shut down gossip")
+}
+
+func (g *Gossiper) gossipLoop() {
+ plog.Info("Starting gossip loop")
+
+ // create a ticker that will tick each GossipInterval milliseconds
+ // we do not use sleep as sleep puts the goroutine out of the scheduler
+ // P, and we do not want our Gs to be scheduled out from the running Ms
+ ticker := time.NewTicker(g.cluster.Config.GossipInterval)
+breakLoop:
+ for !g.cluster.ActorSystem.IsStopped() {
+ select {
+ case <-g.close:
+ plog.Info("Stopping Gossip Loop")
+ break breakLoop
+ case <-ticker.C:
+
+ g.blockExpiredHeartbeats()
+ g.blockGracefullyLeft()
+
+ g.SetState(HearthbeatKey, &MemberHeartbeat{
+ // todo collect the actor statistics
+ ActorStatistics: &ActorStatistics{},
+ })
+ g.SendState()
+ }
+ }
+}
+
+// blockExpiredHeartbeats blocks members that have not sent a heartbeat for a long time
+func (g *Gossiper) blockExpiredHeartbeats() {
+ if g.cluster.Config.GossipInterval == 0 {
+ return
+ }
+ t, err := g.GetState(HearthbeatKey)
+ if err != nil {
+ plog.Error("Could not get heartbeat state", log.Error(err))
+ return
+ }
+
+ blockList := remote.GetRemote(g.cluster.ActorSystem).BlockList()
+
+ blocked := make([]string, 0)
+
+ for k, v := range t {
+ if k != g.cluster.ActorSystem.ID &&
+ !blockList.IsBlocked(k) &&
+ time.Now().Sub(time.UnixMilli(v.LocalTimestampUnixMilliseconds)) > g.cluster.Config.HeartbeatExpiration {
+ blocked = append(blocked, k)
+ }
+ }
+
+ if len(blocked) > 0 {
+ plog.Info("Blocking members due to expired heartbeat", log.String("members", strings.Join(blocked, ",")))
+ blockList.Block(blocked...)
+ }
+}
+
+// blockGracefullyLeft blocking members due to gracefully leaving
+func (g *Gossiper) blockGracefullyLeft() {
+ t, err := g.GetState(GracefullyLeftKey)
+ if err != nil {
+ plog.Error("Could not get gracefully left members", log.Error(err))
+ return
+ }
+
+ blockList := remote.GetRemote(g.cluster.ActorSystem).BlockList()
+
+ gracefullyLeft := make([]string, 0)
+ for k := range t {
+ if !blockList.IsBlocked(k) && k != g.cluster.ActorSystem.ID {
+ gracefullyLeft = append(gracefullyLeft, k)
+ }
+ }
+ if len(gracefullyLeft) > 0 {
+ plog.Info("Blocking members due to gracefully leaving", log.String("members", strings.Join(gracefullyLeft, ",")))
+ blockList.Block(gracefullyLeft...)
+ }
+}
+
+func (g *Gossiper) throttledLog(counter int32) {
+ plog.Debug(fmt.Sprintf("[Gossiper] Gossiper Setting State to %s", g.pid), log.Int("throttled", int(counter)))
+}
diff --git a/cluster/grain.go b/cluster/grain.go
new file mode 100644
index 0000000000000000000000000000000000000000..3834fbde2a4303a7fe435f79d85752b28f9c2381
--- /dev/null
+++ b/cluster/grain.go
@@ -0,0 +1,65 @@
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type GrainCallConfig struct {
+ RetryCount int
+ Timeout time.Duration
+ RetryAction func(n int)
+ Context actor.SenderContext
+}
+
+type GrainCallOption func(config *GrainCallConfig)
+
+var defaultGrainCallOptions *GrainCallConfig
+
+func DefaultGrainCallConfig(cluster *Cluster) *GrainCallConfig {
+ if defaultGrainCallOptions == nil {
+ defaultGrainCallOptions = NewGrainCallOptions(cluster)
+ }
+ return defaultGrainCallOptions
+}
+
+func NewGrainCallOptions(cluster *Cluster) *GrainCallConfig {
+ return &GrainCallConfig{
+ RetryCount: 10,
+ Timeout: cluster.Config.RequestTimeoutTime,
+ RetryAction: func(i int) {
+ i++
+ time.Sleep(time.Duration(i * i * 50))
+ },
+ }
+}
+
+func WithTimeout(timeout time.Duration) GrainCallOption {
+ return func(config *GrainCallConfig) {
+ config.Timeout = timeout
+ }
+}
+
+func WithRetry(count int) GrainCallOption {
+ return func(config *GrainCallConfig) {
+ config.RetryCount = count
+ }
+}
+
+func WithRetryAction(act func(i int)) GrainCallOption {
+ return func(config *GrainCallConfig) {
+ config.RetryAction = act
+ }
+}
+
+func WithContext(ctx actor.SenderContext) GrainCallOption {
+ return func(config *GrainCallConfig) {
+ config.Context = ctx
+ }
+}
+
+type ClusterInit struct {
+ Identity *ClusterIdentity
+ Cluster *Cluster
+}
diff --git a/cluster/grain.pb.go b/cluster/grain.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..d43494fe571fe603693bf1964c3a0d9f9ee4b0ec
--- /dev/null
+++ b/cluster/grain.pb.go
@@ -0,0 +1,302 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: grain.proto
+
+package cluster
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GrainRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MethodIndex int32 `protobuf:"varint,1,opt,name=method_index,json=methodIndex,proto3" json:"method_index,omitempty"`
+ MessageData []byte `protobuf:"bytes,2,opt,name=message_data,json=messageData,proto3" json:"message_data,omitempty"`
+ MessageTypeName string `protobuf:"bytes,3,opt,name=message_type_name,json=messageTypeName,proto3" json:"message_type_name,omitempty"`
+}
+
+func (x *GrainRequest) Reset() {
+ *x = GrainRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_grain_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GrainRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GrainRequest) ProtoMessage() {}
+
+func (x *GrainRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_grain_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GrainRequest.ProtoReflect.Descriptor instead.
+func (*GrainRequest) Descriptor() ([]byte, []int) {
+ return file_grain_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GrainRequest) GetMethodIndex() int32 {
+ if x != nil {
+ return x.MethodIndex
+ }
+ return 0
+}
+
+func (x *GrainRequest) GetMessageData() []byte {
+ if x != nil {
+ return x.MessageData
+ }
+ return nil
+}
+
+func (x *GrainRequest) GetMessageTypeName() string {
+ if x != nil {
+ return x.MessageTypeName
+ }
+ return ""
+}
+
+type GrainResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MessageData []byte `protobuf:"bytes,1,opt,name=message_data,json=messageData,proto3" json:"message_data,omitempty"`
+ MessageTypeName string `protobuf:"bytes,2,opt,name=message_type_name,json=messageTypeName,proto3" json:"message_type_name,omitempty"`
+}
+
+func (x *GrainResponse) Reset() {
+ *x = GrainResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_grain_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GrainResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GrainResponse) ProtoMessage() {}
+
+func (x *GrainResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_grain_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GrainResponse.ProtoReflect.Descriptor instead.
+func (*GrainResponse) Descriptor() ([]byte, []int) {
+ return file_grain_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GrainResponse) GetMessageData() []byte {
+ if x != nil {
+ return x.MessageData
+ }
+ return nil
+}
+
+func (x *GrainResponse) GetMessageTypeName() string {
+ if x != nil {
+ return x.MessageTypeName
+ }
+ return ""
+}
+
+type GrainErrorResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Err string `protobuf:"bytes,1,opt,name=err,proto3" json:"err,omitempty"`
+}
+
+func (x *GrainErrorResponse) Reset() {
+ *x = GrainErrorResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_grain_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GrainErrorResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GrainErrorResponse) ProtoMessage() {}
+
+func (x *GrainErrorResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_grain_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GrainErrorResponse.ProtoReflect.Descriptor instead.
+func (*GrainErrorResponse) Descriptor() ([]byte, []int) {
+ return file_grain_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GrainErrorResponse) GetErr() string {
+ if x != nil {
+ return x.Err
+ }
+ return ""
+}
+
+var File_grain_proto protoreflect.FileDescriptor
+
+var file_grain_proto_rawDesc = []byte{
+ 0x0a, 0x0b, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x80, 0x01, 0x0a, 0x0c, 0x47, 0x72, 0x61, 0x69, 0x6e,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x74, 0x68, 0x6f,
+ 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d,
+ 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,
+ 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a,
+ 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61,
+ 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+ 0x65, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5e, 0x0a, 0x0d, 0x47, 0x72, 0x61,
+ 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
+ 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a,
+ 0x11, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61,
+ 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+ 0x65, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x12, 0x47, 0x72, 0x61,
+ 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
+ 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72,
+ 0x72, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61,
+ 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62,
+ 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_grain_proto_rawDescOnce sync.Once
+ file_grain_proto_rawDescData = file_grain_proto_rawDesc
+)
+
+func file_grain_proto_rawDescGZIP() []byte {
+ file_grain_proto_rawDescOnce.Do(func() {
+ file_grain_proto_rawDescData = protoimpl.X.CompressGZIP(file_grain_proto_rawDescData)
+ })
+ return file_grain_proto_rawDescData
+}
+
+var file_grain_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_grain_proto_goTypes = []interface{}{
+ (*GrainRequest)(nil), // 0: cluster.GrainRequest
+ (*GrainResponse)(nil), // 1: cluster.GrainResponse
+ (*GrainErrorResponse)(nil), // 2: cluster.GrainErrorResponse
+}
+var file_grain_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_grain_proto_init() }
+func file_grain_proto_init() {
+ if File_grain_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_grain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GrainRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_grain_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GrainResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_grain_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GrainErrorResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_grain_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 3,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_grain_proto_goTypes,
+ DependencyIndexes: file_grain_proto_depIdxs,
+ MessageInfos: file_grain_proto_msgTypes,
+ }.Build()
+ File_grain_proto = out.File
+ file_grain_proto_rawDesc = nil
+ file_grain_proto_goTypes = nil
+ file_grain_proto_depIdxs = nil
+}
diff --git a/cluster/grain.proto b/cluster/grain.proto
new file mode 100644
index 0000000000000000000000000000000000000000..85ad4104c7121aae2a3f6cd7ae71ea62d6946166
--- /dev/null
+++ b/cluster/grain.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+package cluster;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster";
+
+message GrainRequest {
+ int32 method_index = 1;
+ bytes message_data = 2;
+ string message_type_name = 3;
+}
+
+message GrainResponse {
+ bytes message_data = 1;
+ string message_type_name = 2;
+}
+
+message GrainErrorResponse {
+ string err = 1;
+}
diff --git a/cluster/grain_context.go b/cluster/grain_context.go
new file mode 100644
index 0000000000000000000000000000000000000000..05200ecb9907b554addb86116c1f098990dff768
--- /dev/null
+++ b/cluster/grain_context.go
@@ -0,0 +1,41 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type GrainContext interface {
+ actor.Context
+
+ Identity() string
+ Kind() string
+ Cluster() *Cluster
+}
+
+var _ actor.Context = GrainContext(&grainContextImpl{})
+
+type grainContextImpl struct {
+ actor.Context
+ ci *ClusterIdentity
+ cluster *Cluster
+}
+
+func (g grainContextImpl) Identity() string {
+ return g.ci.Identity
+}
+
+func (g grainContextImpl) Kind() string {
+ return g.ci.Kind
+}
+
+func (g grainContextImpl) Cluster() *Cluster {
+ return g.cluster
+}
+
+func NewGrainContext(context actor.Context, identity *ClusterIdentity, cluster *Cluster) GrainContext {
+ return &grainContextImpl{
+ Context: context,
+ ci: identity,
+ cluster: cluster,
+ }
+}
diff --git a/cluster/identity_lookup.go b/cluster/identity_lookup.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e1858340190f4f6b3035cbc8f8cea62b699e8a9
--- /dev/null
+++ b/cluster/identity_lookup.go
@@ -0,0 +1,89 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// IdentityLookup contains
+type IdentityLookup interface {
+ Get(clusterIdentity *ClusterIdentity) *actor.PID
+
+ RemovePid(clusterIdentity *ClusterIdentity, pid *actor.PID)
+
+ Setup(cluster *Cluster, kinds []string, isClient bool)
+
+ Shutdown()
+}
+
+// StorageLookup contains
+type StorageLookup interface {
+ TryGetExistingActivation(clusterIdentity *ClusterIdentity) *StoredActivation
+
+ TryAcquireLock(clusterIdentity *ClusterIdentity) *SpawnLock
+
+ WaitForActivation(clusterIdentity *ClusterIdentity) *StoredActivation
+
+ RemoveLock(spawnLock SpawnLock)
+
+ StoreActivation(memberID string, spawnLock *SpawnLock, pid *actor.PID)
+
+ RemoveActivation(pid *SpawnLock)
+
+ RemoveMemberId(memberID string)
+}
+
+// SpawnLock contains
+type SpawnLock struct {
+ LockID string
+ ClusterIdentity *ClusterIdentity
+}
+
+func newSpawnLock(lockID string, clusterIdentity *ClusterIdentity) *SpawnLock {
+ this := &SpawnLock{
+ LockID: lockID,
+ ClusterIdentity: clusterIdentity,
+ }
+
+ return this
+}
+
+// StoredActivation contains
+type StoredActivation struct {
+ Pid string
+ MemberID string
+}
+
+func newStoredActivation(pid string, memberID string) *StoredActivation {
+ this := &StoredActivation{
+ Pid: pid,
+ MemberID: memberID,
+ }
+
+ return this
+}
+
+// GetPid contains
+type GetPid struct {
+ ClusterIdentity *ClusterIdentity
+}
+
+func newGetPid(clusterIdentity *ClusterIdentity) *GetPid {
+ this := &GetPid{
+ ClusterIdentity: clusterIdentity,
+ }
+
+ return this
+}
+
+// PidResult contains
+type PidResult struct {
+ Pid *actor.PID
+}
+
+func newPidResult(p *actor.PID) *PidResult {
+ this := &PidResult{
+ Pid: p,
+ }
+
+ return this
+}
diff --git a/cluster/identity_storage_lookup.go b/cluster/identity_storage_lookup.go
new file mode 100644
index 0000000000000000000000000000000000000000..c234fd009c2ec280469400bc14b3f946fc3508b7
--- /dev/null
+++ b/cluster/identity_storage_lookup.go
@@ -0,0 +1,65 @@
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+const (
+ placementActorName = "placement-activator"
+ pidClusterIdentityStartIndex = len(placementActorName) + 1
+)
+
+// IdentityStorageLookup contains
+type IdentityStorageLookup struct {
+ Storage StorageLookup
+ cluster *Cluster
+ isClient bool
+ placementActor *actor.PID
+ system *actor.ActorSystem
+ router *actor.PID
+ memberID string
+}
+
+func newIdentityStorageLookup(storage StorageLookup) *IdentityStorageLookup {
+ this := &IdentityStorageLookup{
+ Storage: storage,
+ }
+ return this
+}
+
+// RemoveMember from identity storage
+func (i *IdentityStorageLookup) RemoveMember(memberID string) {
+ i.Storage.RemoveMemberId(memberID)
+}
+
+// RemotePlacementActor returns the PID of the remote placement actor
+func RemotePlacementActor(address string) *actor.PID {
+ return actor.NewPID(address, placementActorName)
+}
+
+//
+// Interface: IdentityLookup
+//
+
+// Get returns a PID for a given ClusterIdentity
+func (id *IdentityStorageLookup) Get(clusterIdentity *ClusterIdentity) *actor.PID {
+ msg := newGetPid(clusterIdentity)
+ timeout := 5 * time.Second
+
+ res, _ := id.system.Root.RequestFuture(id.router, msg, timeout).Result()
+ response := res.(*actor.Future)
+
+ return response.PID()
+}
+
+func (id *IdentityStorageLookup) Setup(cluster *Cluster, kinds []string, isClient bool) {
+ id.cluster = cluster
+ id.system = cluster.ActorSystem
+ id.memberID = cluster.ActorSystem.ID
+
+ // workerProps := actor.PropsFromProducer(func() actor.Actor { return newIdentityStorageWorker(identity) })
+
+ // routerProps := identity.system.Root.(workerProps, 50);
+}
diff --git a/cluster/identity_storage_worker.go b/cluster/identity_storage_worker.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ae1303673233689ffd82fd25b52e5034788ec5c
--- /dev/null
+++ b/cluster/identity_storage_worker.go
@@ -0,0 +1,47 @@
+package cluster
+
+import (
+ "log"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type IdentityStorageWorker struct {
+ cluster *Cluster
+ lookup *IdentityStorageLookup
+ storage StorageLookup
+}
+
+func newIdentityStorageWorker(storageLookup *IdentityStorageLookup) *IdentityStorageWorker {
+ this := &IdentityStorageWorker{
+ cluster: storageLookup.cluster,
+ lookup: storageLookup,
+ storage: storageLookup.Storage,
+ }
+ return this
+}
+
+// Receive func
+func (ids *IdentityStorageWorker) Receive(c actor.Context) {
+ m := c.Message()
+ getPid, ok := m.(GetPid)
+
+ if !ok {
+ return
+ }
+
+ if c.Sender() == nil {
+ log.Println("No sender in GetPid request")
+ return
+ }
+
+ existing, _ := ids.cluster.PidCache.Get(getPid.ClusterIdentity.Identity, getPid.ClusterIdentity.Kind)
+
+ if existing != nil {
+ log.Printf("Found %s in pidcache", m.(GetPid).ClusterIdentity.ToShortString())
+ c.Respond(newPidResult(existing))
+ }
+
+ return
+ // continue
+}
diff --git a/cluster/identitylookup/disthash/identity_lookup.go b/cluster/identitylookup/disthash/identity_lookup.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5911a7d9d630b972bf713d466b8bb2363ef750f
--- /dev/null
+++ b/cluster/identitylookup/disthash/identity_lookup.go
@@ -0,0 +1,35 @@
+package disthash
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+type IdentityLookup struct {
+ partitionManager *Manager
+}
+
+func (p *IdentityLookup) Get(clusterIdentity *cluster.ClusterIdentity) *actor.PID {
+ return p.partitionManager.Get(clusterIdentity)
+}
+
+func (p *IdentityLookup) RemovePid(clusterIdentity *cluster.ClusterIdentity, pid *actor.PID) {
+ activationTerminated := &cluster.ActivationTerminated{
+ Pid: pid,
+ ClusterIdentity: clusterIdentity,
+ }
+ p.partitionManager.cluster.MemberList.BroadcastEvent(activationTerminated, true)
+}
+
+func (p *IdentityLookup) Setup(cluster *cluster.Cluster, kinds []string, isClient bool) {
+ p.partitionManager = newPartitionManager(cluster)
+ p.partitionManager.Start()
+}
+
+func (p *IdentityLookup) Shutdown() {
+ p.partitionManager.Stop()
+}
+
+func New() cluster.IdentityLookup {
+ return &IdentityLookup{}
+}
diff --git a/cluster/identitylookup/disthash/log.go b/cluster/identitylookup/disthash/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c6f269aa942d50b7483258626f6a91f2c10c09f
--- /dev/null
+++ b/cluster/identitylookup/disthash/log.go
@@ -0,0 +1,14 @@
+package disthash
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+var plog = log.New(log.DefaultLevel, "[DISTHASH]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/identitylookup/disthash/manager.go b/cluster/identitylookup/disthash/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b0fd7ce6bb5f533d15e0f066a2b35506cf54273
--- /dev/null
+++ b/cluster/identitylookup/disthash/manager.go
@@ -0,0 +1,99 @@
+package disthash
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ clustering "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+const (
+ PartitionActivatorActorName = "partition-activator"
+)
+
+type Manager struct {
+ cluster *clustering.Cluster
+ topologySub *eventstream.Subscription
+ placementActor *actor.PID
+ rdv *clustering.Rendezvous
+}
+
+func newPartitionManager(c *clustering.Cluster) *Manager {
+ return &Manager{
+ cluster: c,
+ rdv: clustering.NewRendezvous(),
+ }
+}
+
+func (pm *Manager) Start() {
+ plog.Info("Started partition manager")
+ system := pm.cluster.ActorSystem
+
+ activatorProps := actor.PropsFromProducer(func() actor.Actor { return newPlacementActor(pm.cluster, pm) })
+ pm.placementActor, _ = system.Root.SpawnNamed(activatorProps, PartitionActivatorActorName)
+ plog.Info("Started partition placement actor")
+
+ pm.topologySub = system.EventStream.
+ Subscribe(func(ev interface{}) {
+ if topology, ok := ev.(*clustering.ClusterTopology); ok {
+ pm.onClusterTopology(topology)
+ }
+ })
+}
+
+func (pm *Manager) Stop() {
+ system := pm.cluster.ActorSystem
+ system.EventStream.Unsubscribe(pm.topologySub)
+
+ err := system.Root.PoisonFuture(pm.placementActor).Wait()
+ if err != nil {
+ plog.Error("Failed to shutdown partition placement actor", log.Error(err))
+ }
+
+ plog.Info("Stopped PartitionManager")
+}
+
+func (pm *Manager) PidOfActivatorActor(addr string) *actor.PID {
+ return actor.NewPID(addr, PartitionActivatorActorName)
+}
+
+func (pm *Manager) onClusterTopology(tplg *clustering.ClusterTopology) {
+ plog.Info("onClusterTopology", log.Uint64("topology-hash", tplg.TopologyHash))
+
+ for _, m := range tplg.Members {
+ plog.Info("Got member ", log.String("MemberId", m.Id))
+ for _, k := range m.Kinds {
+ plog.Info("" + m.Id + " - " + k)
+ }
+ }
+
+ pm.rdv = clustering.NewRendezvous()
+ pm.rdv.UpdateMembers(tplg.Members)
+ pm.cluster.ActorSystem.Root.Send(pm.placementActor, tplg)
+}
+
+func (pm *Manager) Get(identity *clustering.ClusterIdentity) *actor.PID {
+ ownerAddress := pm.rdv.GetByClusterIdentity(identity)
+
+ if ownerAddress == "" {
+ return nil
+ }
+
+ identityOwnerPid := pm.PidOfActivatorActor(ownerAddress)
+ request := &clustering.ActivationRequest{
+ ClusterIdentity: identity,
+ RequestId: "aaaa",
+ }
+ future := pm.cluster.ActorSystem.Root.RequestFuture(identityOwnerPid, request, 5*time.Second)
+ res, err := future.Result()
+ if err != nil {
+ return nil
+ }
+ typed, ok := res.(*clustering.ActivationResponse)
+ if !ok {
+ return nil
+ }
+ return typed.Pid
+}
diff --git a/cluster/identitylookup/disthash/placement_actor.go b/cluster/identitylookup/disthash/placement_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..a85efa2a6dbde3ee00d9911dee6f5792ad8fb98c
--- /dev/null
+++ b/cluster/identitylookup/disthash/placement_actor.go
@@ -0,0 +1,138 @@
+package disthash
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ clustering "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+type GrainMeta struct {
+ ID *clustering.ClusterIdentity
+ PID *actor.PID
+}
+
+type placementActor struct {
+ cluster *clustering.Cluster
+ partitionManager *Manager
+ actors map[string]GrainMeta
+}
+
+func newPlacementActor(c *clustering.Cluster, pm *Manager) *placementActor {
+ return &placementActor{
+ cluster: c,
+ partitionManager: pm,
+ actors: map[string]GrainMeta{},
+ }
+}
+
+func (p *placementActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ plog.Info("Placement actor started")
+ case *actor.Stopping:
+ plog.Info("Placement actor stopping")
+ p.onStopping(ctx)
+ case *actor.Stopped:
+ plog.Info("Placement actor stopped")
+ case *actor.Terminated:
+ p.onTerminated(msg, ctx)
+ case *clustering.ActivationRequest:
+ p.onActivationRequest(msg, ctx)
+ case *clustering.ClusterTopology:
+ p.onClusterTopology(msg, ctx)
+ default:
+ plog.Error("Invalid message", log.TypeOf("type", msg), log.PID("sender", ctx.Sender()))
+ }
+}
+
+func (p *placementActor) onTerminated(msg *actor.Terminated, ctx actor.Context) {
+ found, key, meta := p.pidToMeta(msg.Who)
+
+ activationTerminated := &clustering.ActivationTerminated{
+ Pid: msg.Who,
+ ClusterIdentity: meta.ID,
+ }
+ p.partitionManager.cluster.MemberList.BroadcastEvent(activationTerminated, true)
+
+ if found {
+ delete(p.actors, *key)
+ }
+}
+
+func (p *placementActor) onStopping(ctx actor.Context) {
+ futures := make(map[string]*actor.Future, len(p.actors))
+
+ for key, meta := range p.actors {
+ futures[key] = ctx.PoisonFuture(meta.PID)
+ }
+
+ for key, future := range futures {
+ err := future.Wait()
+ if err != nil {
+ plog.Error("Failed to poison actor", log.String("identity", key), log.Error(err))
+ }
+ }
+}
+
+func (p *placementActor) onActivationRequest(msg *clustering.ActivationRequest, ctx actor.Context) {
+ key := msg.ClusterIdentity.AsKey()
+ meta, found := p.actors[key]
+ if found {
+ response := &clustering.ActivationResponse{
+ Pid: meta.PID,
+ }
+ ctx.Respond(response)
+ return
+ }
+
+ clusterKind := p.cluster.GetClusterKind(msg.ClusterIdentity.Kind)
+ if clusterKind == nil {
+ plog.Error("Unknown cluster kind", log.String("kind", msg.ClusterIdentity.Kind))
+
+ // TODO: what to do here?
+ ctx.Respond(nil)
+ return
+ }
+
+ props := clustering.WithClusterIdentity(clusterKind.Props, msg.ClusterIdentity)
+
+ pid := ctx.SpawnPrefix(props, msg.ClusterIdentity.Identity)
+
+ p.actors[key] = GrainMeta{
+ ID: msg.ClusterIdentity,
+ PID: pid,
+ }
+
+ response := &clustering.ActivationResponse{
+ Pid: pid,
+ }
+
+ ctx.Respond(response)
+}
+
+func (p *placementActor) pidToMeta(pid *actor.PID) (bool, *string, *GrainMeta) {
+ for k, v := range p.actors {
+ if v.PID == pid {
+ return true, &k, &v
+ }
+ }
+ return false, nil, nil
+}
+
+func (p *placementActor) onClusterTopology(msg *clustering.ClusterTopology, ctx actor.Context) {
+ rdv := clustering.NewRendezvous()
+ rdv.UpdateMembers(msg.Members)
+ myAddress := p.cluster.ActorSystem.Address()
+ for identity, meta := range p.actors {
+ ownerAddress := rdv.GetByIdentity(identity)
+ if ownerAddress == myAddress {
+
+ plog.Debug("Actor stays", log.String("identity", identity), log.String("owner", ownerAddress), log.String("me", myAddress))
+ continue
+ }
+
+ plog.Debug("Actor moved", log.String("identity", identity), log.String("owner", ownerAddress), log.String("me", myAddress))
+
+ ctx.Poison(meta.PID)
+ }
+}
diff --git a/cluster/identitylookup/partition/identity_actor.go b/cluster/identitylookup/partition/identity_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..f7b252025607d0fdc29eb433e82306b2bfd87b72
--- /dev/null
+++ b/cluster/identitylookup/partition/identity_actor.go
@@ -0,0 +1,211 @@
+package partition
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ clustering "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+// This actor is responsible to keep track of identities owned by this member
+// it does not manage the cluster spawned actors itself, only identity->remote PID management
+// TLDR; this is a partition/bucket in the distributed hash table which makes up the identity lookup
+//
+// for spawning/activating cluster actors see PartitionActivator.cs
+
+type identityActor struct {
+ cluster *clustering.Cluster
+ partitionManager *Manager
+ lookup map[string]*actor.PID
+ spawns map[string]*actor.Future
+ topologyHash uint64
+ handoverTimeout time.Duration
+ rdv *clustering.Rendezvous
+}
+
+func newIdentityActor(c *clustering.Cluster, p *Manager) *identityActor {
+ return &identityActor{
+ cluster: c,
+ partitionManager: p,
+ handoverTimeout: 10 * time.Second,
+ lookup: map[string]*actor.PID{},
+ spawns: map[string]*actor.Future{},
+ }
+}
+
+func (p *identityActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ p.onStart(ctx)
+ case *actor.Stopped:
+ p.onStopped()
+ case *clustering.ActivationRequest:
+ p.onActivationRequest(msg, ctx)
+ case *clustering.ActivationTerminated:
+ p.onActivationTerminated(msg)
+ case *clustering.ClusterTopology:
+ p.onClusterTopology(msg, ctx)
+ default:
+ plog.Error("Invalid message", log.TypeOf("type", msg), log.PID("sender", ctx.Sender()))
+ }
+}
+
+func (p *identityActor) onStart(ctx actor.Context) {
+ plog.Debug("Started PartitionIdentity")
+ self := ctx.Self()
+ ctx.ActorSystem().EventStream.Subscribe(func(evt interface{}) {
+ if at, ok := evt.(*clustering.ActivationTerminated); ok {
+ p.cluster.ActorSystem.Root.Send(self, at)
+ }
+ })
+}
+
+func (p *identityActor) onStopped() {
+ plog.Info("Stopped PartitionIdentity")
+}
+
+func (p *identityActor) onActivationRequest(msg *clustering.ActivationRequest, ctx actor.Context) {
+ ownerAddress := p.rdv.GetByClusterIdentity(msg.ClusterIdentity)
+
+ // should I own it?
+ if ownerAddress != ctx.Self().Address {
+ ownerPid := p.partitionManager.PidOfIdentityActor(ownerAddress)
+ ctx.Forward(ownerPid)
+ return
+ }
+
+ // do I already own it?
+ if pid, ok := p.lookup[msg.ClusterIdentity.AsKey()]; ok {
+ respondActivation(pid, ctx)
+ return
+ }
+
+ // Get activator
+ activatorAddress := p.cluster.MemberList.GetActivatorMember(msg.ClusterIdentity.Kind, ctx.Sender().Address)
+ activator := p.partitionManager.PidOfActivatorActor(activatorAddress)
+
+ // No activator found, bail out and respond empty
+ if activator == nil {
+ respondEmptyActivation(ctx)
+ return
+ }
+
+ // What is this?
+ // in case the actor of msg.Name is not yet spawned. there could be multiple re-entrant
+ // messages requesting it, we just reuse the same task for all those
+ // once spawned, the key is removed from this dict
+ res, ok := p.spawns[msg.ClusterIdentity.AsKey()]
+ if !ok {
+ res = p.spawnRemoteActor(msg, activatorAddress)
+ p.spawns[msg.ClusterIdentity.AsKey()] = res
+ }
+
+ // execution ends here. context.ReenterAfter is invoked once the task completes
+ // but still within the actors sequential execution
+ // but other messages could have been processed in between
+ // Await SpawningProcess
+ ctx.ReenterAfter(res, func(res interface{}, err error) {
+ delete(p.spawns, msg.ClusterIdentity.AsKey())
+
+ ar, ok := res.(*clustering.ActivationResponse)
+ if !ok {
+ // spawn failed, respond empty
+ respondEmptyActivation(ctx)
+ return
+ }
+
+ // do I already own it?
+ if pid, ok := p.lookup[msg.ClusterIdentity.AsKey()]; ok {
+ respondActivation(pid, ctx)
+ return
+ }
+
+ p.lookup[msg.ClusterIdentity.AsKey()] = ar.Pid
+
+ respondActivation(ar.Pid, ctx)
+ })
+}
+
+func respondActivation(pid *actor.PID, ctx actor.Context) {
+ response := &clustering.ActivationResponse{
+ Pid: pid,
+ }
+
+ ctx.Respond(response)
+}
+
+func respondEmptyActivation(ctx actor.Context) {
+ response := &clustering.ActivationResponse{
+ Pid: nil,
+ }
+ ctx.Respond(response)
+}
+
+func (p *identityActor) onActivationTerminated(msg *clustering.ActivationTerminated) {
+ // //we get this via broadcast to all nodes, remove if we have it, or ignore
+ key := msg.ClusterIdentity.AsKey()
+ _, ok := p.spawns[key]
+ if ok {
+ return
+ }
+
+ // Logger.LogDebug("[PartitionIdentityActor] Terminated {Pid}", msg.Pid);
+ p.cluster.PidCache.RemoveByValue(msg.ClusterIdentity.Identity, msg.ClusterIdentity.Kind, msg.Pid)
+ delete(p.lookup, key)
+}
+
+func (p *identityActor) onClusterTopology(msg *clustering.ClusterTopology, ctx actor.Context) {
+ // await _cluster.MemberList.TopologyConsensus();
+ if p.topologyHash == msg.TopologyHash {
+ return
+ }
+
+ members := msg.Members
+ p.rdv = clustering.NewRendezvous()
+ p.rdv.UpdateMembers(members)
+ p.lookup = map[string]*actor.PID{}
+ futures := make([]*actor.Future, 0)
+
+ requestMsg := &clustering.IdentityHandoverRequest{
+ CurrentTopology: &clustering.IdentityHandoverRequest_Topology{
+ Members: msg.Members,
+ TopologyHash: msg.TopologyHash,
+ },
+
+ Address: ctx.Self().Address,
+ }
+
+ for _, m := range members {
+ placementPid := p.partitionManager.PidOfActivatorActor(m.Address())
+ future := ctx.RequestFuture(placementPid, requestMsg, 5*time.Second)
+
+ futures = append(futures, future)
+ }
+
+ for _, f := range futures {
+ res, _ := f.Result()
+ if response, ok := res.(*clustering.IdentityHandover); ok {
+ for _, activation := range response.Actors {
+ p.takeOwnership(activation)
+ }
+ }
+ }
+}
+
+func (p *identityActor) takeOwnership(activation *clustering.Activation) {
+ key := activation.ClusterIdentity.AsKey()
+ if existing, ok := p.lookup[key]; ok {
+ if existing.Address == activation.Pid.Address {
+ return
+ }
+ }
+
+ p.lookup[key] = activation.Pid
+}
+
+func (p *identityActor) spawnRemoteActor(msg *clustering.ActivationRequest, address string) *actor.Future {
+ activator := p.partitionManager.PidOfActivatorActor(address)
+ future := p.cluster.ActorSystem.Root.RequestFuture(activator, msg, 5*time.Second)
+ return future
+}
diff --git a/cluster/identitylookup/partition/identity_actor_test.go b/cluster/identitylookup/partition/identity_actor_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..54d1e086698b3b900a832c0b403734a61084d3db
--- /dev/null
+++ b/cluster/identitylookup/partition/identity_actor_test.go
@@ -0,0 +1,11 @@
+package partition
+
+// func TestPartitionIdentityActor_handleClusterTopology(t *testing.T) {
+// assert := assert.New(t)
+// members := _newTopologyEventForTest(1)
+// cluster := _newClusterForTest("test-partition-identityactor")
+// partitionManager := newPartitionManager(cluster)
+// partitionManager.StartMember()
+// tplg := ClusterTopology{Members: members, EventId: 1}
+// cluster.ActorSystem.EventStream.Publish(&tplg)
+// }
diff --git a/cluster/identitylookup/partition/identity_lookup.go b/cluster/identitylookup/partition/identity_lookup.go
new file mode 100644
index 0000000000000000000000000000000000000000..6a5420b970262b2370b4f36747af6f0aed09246d
--- /dev/null
+++ b/cluster/identitylookup/partition/identity_lookup.go
@@ -0,0 +1,35 @@
+package partition
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+)
+
+type IdentityLookup struct {
+ partitionManager *Manager
+}
+
+func (p *IdentityLookup) Get(clusterIdentity *cluster.ClusterIdentity) *actor.PID {
+ return p.partitionManager.Get(clusterIdentity)
+}
+
+func (p *IdentityLookup) RemovePid(clusterIdentity *cluster.ClusterIdentity, pid *actor.PID) {
+ activationTerminated := &cluster.ActivationTerminated{
+ Pid: pid,
+ ClusterIdentity: clusterIdentity,
+ }
+ p.partitionManager.cluster.MemberList.BroadcastEvent(activationTerminated, true)
+}
+
+func (p *IdentityLookup) Setup(cluster *cluster.Cluster, kinds []string, isClient bool) {
+ p.partitionManager = newPartitionManager(cluster)
+ p.partitionManager.Start()
+}
+
+func (p *IdentityLookup) Shutdown() {
+ p.partitionManager.Stop()
+}
+
+func New() cluster.IdentityLookup {
+ return &IdentityLookup{}
+}
diff --git a/cluster/identitylookup/partition/log.go b/cluster/identitylookup/partition/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f5dc6c0558a42eb54c5e7f98fc3e454a8c33b33
--- /dev/null
+++ b/cluster/identitylookup/partition/log.go
@@ -0,0 +1,14 @@
+package partition
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+var plog = log.New(log.DefaultLevel, "[PARTITION]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/identitylookup/partition/manager.go b/cluster/identitylookup/partition/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..643e2fca32f2938f524c55f77469cb566d1d1ab5
--- /dev/null
+++ b/cluster/identitylookup/partition/manager.go
@@ -0,0 +1,110 @@
+package partition
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ clustering "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+const (
+ ActorNameIdentity = "partition"
+ ActorNamePlacement = "partition-activator"
+)
+
+type Manager struct {
+ cluster *clustering.Cluster
+ topologySub *eventstream.Subscription
+ identityActor *actor.PID
+ placementActor *actor.PID
+ rdv *clustering.Rendezvous
+}
+
+func newPartitionManager(c *clustering.Cluster) *Manager {
+ return &Manager{
+ cluster: c,
+ }
+}
+
+func (pm *Manager) Start() {
+ plog.Info("Started partition manager")
+ system := pm.cluster.ActorSystem
+
+ identityProps := actor.PropsFromProducer(func() actor.Actor { return newIdentityActor(pm.cluster, pm) })
+ pm.identityActor, _ = system.Root.SpawnNamed(identityProps, ActorNameIdentity)
+ plog.Info("Started partition identity actor")
+
+ activatorProps := actor.PropsFromProducer(func() actor.Actor { return newPlacementActor(pm.cluster, pm) })
+ pm.placementActor, _ = system.Root.SpawnNamed(activatorProps, ActorNamePlacement)
+ plog.Info("Started partition placement actor")
+
+ pm.topologySub = system.EventStream.
+ Subscribe(func(ev interface{}) {
+ // fmt.Printf("PM got event.... %v", ev)
+ if topology, ok := ev.(*clustering.ClusterTopology); ok {
+ pm.onClusterTopology(topology)
+ }
+ })
+}
+
+func (pm *Manager) Stop() {
+ system := pm.cluster.ActorSystem
+ system.EventStream.Unsubscribe(pm.topologySub)
+
+ err := system.Root.PoisonFuture(pm.placementActor).Wait()
+ if err != nil {
+ plog.Error("Failed to shutdown partition placement actor", log.Error(err))
+ }
+
+ plog.Info("Stopped PartitionManager")
+}
+
+func (pm *Manager) PidOfIdentityActor(addr string) *actor.PID {
+ return actor.NewPID(addr, ActorNameIdentity)
+}
+
+func (pm *Manager) PidOfActivatorActor(addr string) *actor.PID {
+ return actor.NewPID(addr, ActorNamePlacement)
+}
+
+func (pm *Manager) onClusterTopology(tplg *clustering.ClusterTopology) {
+ plog.Info("onClusterTopology", log.Uint64("eventId", tplg.TopologyHash))
+
+ for _, m := range tplg.Members {
+ plog.Info("Got member " + m.Id)
+
+ for _, k := range m.Kinds {
+ plog.Info("" + m.Id + " - " + k)
+ }
+ }
+
+ pm.rdv = clustering.NewRendezvous()
+ pm.rdv.UpdateMembers(tplg.Members)
+ pm.cluster.ActorSystem.Root.Send(pm.identityActor, tplg)
+}
+
+func (pm *Manager) Get(identity *clustering.ClusterIdentity) *actor.PID {
+ ownerAddress := pm.rdv.GetByClusterIdentity(identity)
+
+ if ownerAddress == "" {
+ return nil
+ }
+
+ identityOwnerPid := pm.PidOfIdentityActor(ownerAddress)
+ request := &clustering.ActivationRequest{
+ ClusterIdentity: identity,
+ RequestId: "aaaa",
+ }
+ future := pm.cluster.ActorSystem.Root.RequestFuture(identityOwnerPid, request, 5*time.Second)
+ res, err := future.Result()
+ if err != nil {
+ return nil
+ }
+ typed, ok := res.(*clustering.ActivationResponse)
+ if !ok {
+ return nil
+ }
+ return typed.Pid
+}
diff --git a/cluster/identitylookup/partition/partition.puml b/cluster/identitylookup/partition/partition.puml
new file mode 100644
index 0000000000000000000000000000000000000000..69b6ef070b438607b9209bea4268dde79f7011b5
--- /dev/null
+++ b/cluster/identitylookup/partition/partition.puml
@@ -0,0 +1,40 @@
+@startuml
+
+title "Partition Sequence"
+
+participant "IdentityLookup@node1" as lookup #LightGreen
+participant "IdentityActor@node1" as id1 #LightGreen
+participant "PlacementActor@node1" as place1 #LightGreen
+
+participant "IdentityActor@node2" as id2 #Pink
+participant "PlacementActor@node2" as place2 #Pink
+
+
+lookup -> id1: send ActivationRequest
+activate id1
+ id1 -> id1: owner.address = chash(ClusterIdentity)
+ activate id1
+
+alt owner.address == self.address (owner = chash(id))
+ id1 -> place1: forward ActivationRequest
+ activate place1
+ id1 <- place1: respond ActivationResponse
+ deactivate place1
+ deactivate id1
+ lookup <- id1: respond ActivationResponse
+else
+ id1 -> id2: send ActivationRequest
+ activate id2
+ id2 -> id2: owner.address = chash(ClusterIdentity)
+ activate id2
+ id2 -> place2: forward ActivationRequest
+ deactivate id2
+ deactivate id2
+ activate place2
+ id1 <- place2: respond ActivationResponse
+ deactivate place2
+ lookup <- id1: respond ActivationResponse
+ deactivate id1
+end
+
+@enduml
\ No newline at end of file
diff --git a/cluster/identitylookup/partition/placement_actor.go b/cluster/identitylookup/partition/placement_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0b12aa10cfc4aa1c4a727e73f1a7b0b9a7c8e58
--- /dev/null
+++ b/cluster/identitylookup/partition/placement_actor.go
@@ -0,0 +1,144 @@
+package partition
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ clustering "gitee.com/simplexyz/simpleactor-go/cluster"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+type GrainMeta struct {
+ ID *clustering.ClusterIdentity
+ PID *actor.PID
+}
+
+type placementActor struct {
+ cluster *clustering.Cluster
+ partitionManager *Manager
+ actors map[string]GrainMeta
+}
+
+func newPlacementActor(c *clustering.Cluster, pm *Manager) *placementActor {
+ return &placementActor{
+ cluster: c,
+ partitionManager: pm,
+ actors: map[string]GrainMeta{},
+ }
+}
+
+func (p *placementActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Stopping:
+ plog.Info("Placement actor stopping")
+ p.onStopping(ctx)
+ case *actor.Stopped:
+ plog.Info("Placement actor stopped")
+ case *actor.Terminated:
+ p.onTerminated(msg, ctx)
+ case *clustering.IdentityHandoverRequest:
+ p.onIdentityHandoverRequest(msg, ctx)
+ case *clustering.ActivationRequest:
+ p.onActivationRequest(msg, ctx)
+ default:
+ plog.Error("Invalid message", log.TypeOf("type", msg), log.PID("sender", ctx.Sender()))
+ }
+}
+
+func (p *placementActor) onTerminated(msg *actor.Terminated, ctx actor.Context) {
+ found, key, meta := p.pidToMeta(msg.Who)
+
+ activationTerminated := &clustering.ActivationTerminated{
+ Pid: msg.Who,
+ ClusterIdentity: meta.ID,
+ }
+ p.partitionManager.cluster.MemberList.BroadcastEvent(activationTerminated, true)
+
+ if found {
+ delete(p.actors, *key)
+ }
+}
+
+func (p *placementActor) onStopping(ctx actor.Context) {
+ futures := make(map[string]*actor.Future, len(p.actors))
+
+ for key, meta := range p.actors {
+ futures[key] = ctx.PoisonFuture(meta.PID)
+ }
+
+ for key, future := range futures {
+ err := future.Wait()
+ if err != nil {
+ plog.Error("Failed to poison actor", log.String("identity", key), log.Error(err))
+ }
+ }
+}
+
+// this is pure, we do not change any state or actually move anything
+// the requester also provide its own view of the world in terms of members
+// TLDR; we are not using any topology state from this actor itself
+func (p *placementActor) onIdentityHandoverRequest(msg *clustering.IdentityHandoverRequest, ctx actor.Context) {
+ count := 0
+ response := &clustering.IdentityHandover{}
+ requestAddress := ctx.Sender().Address
+ rdv := clustering.NewRendezvous()
+ rdv.UpdateMembers(msg.CurrentTopology.Members)
+ for identity, meta := range p.actors {
+ // who owns this identity according to the requesters memberlist?
+ ownerAddress := rdv.GetByIdentity(identity)
+ // this identity is not owned by the requester
+ if ownerAddress != requestAddress {
+ continue
+ }
+ // _logger.LogDebug("Transfer {Identity} to {newOwnerAddress} -- {TopologyHash}", clusterIdentity, ownerAddress,
+ // msg.TopologyHash
+ // );
+
+ actorToHandOver := &clustering.Activation{
+ ClusterIdentity: meta.ID,
+ Pid: meta.PID,
+ }
+
+ response.Actors = append(response.Actors, actorToHandOver)
+ count++
+ }
+
+ plog.Debug("Transferred ownership to other members", log.Int("count", count))
+ ctx.Respond(response)
+}
+
+func (p *placementActor) onActivationRequest(msg *clustering.ActivationRequest, ctx actor.Context) {
+ key := msg.ClusterIdentity.AsKey()
+ meta, found := p.actors[key]
+ if found {
+ response := &clustering.ActivationResponse{
+ Pid: meta.PID,
+ }
+ ctx.Respond(response)
+ return
+ }
+
+ clusterKind := p.cluster.GetClusterKind(msg.ClusterIdentity.Kind)
+
+ props := clustering.WithClusterIdentity(clusterKind.Props, msg.ClusterIdentity)
+
+ pid := ctx.SpawnPrefix(props, msg.ClusterIdentity.Identity)
+
+ p.actors[key] = GrainMeta{
+ ID: msg.ClusterIdentity,
+ PID: pid,
+ }
+
+ response := &clustering.ActivationResponse{
+ Pid: pid,
+ }
+
+ ctx.Respond(response)
+}
+
+func (p *placementActor) pidToMeta(pid *actor.PID) (bool, *string, *GrainMeta) {
+ for k, v := range p.actors {
+ if v.PID == pid {
+ return true, &k, &v
+ }
+ }
+ return false, nil, nil
+}
diff --git a/cluster/informer.go b/cluster/informer.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb7af343444a6c2cdcc0d27e84eb92f9ec34aba0
--- /dev/null
+++ b/cluster/informer.go
@@ -0,0 +1,292 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+import (
+ "fmt"
+ "math/rand"
+ "reflect"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "github.com/asynkron/gofun/set"
+ "google.golang.org/protobuf/proto"
+)
+
+const (
+ TopologyKey string = "topology"
+ HearthbeatKey string = "heathbeat"
+ GracefullyLeftKey string = "left"
+)
+
+// create and seed a pseudo random numbers generator
+var rnd = rand.New(rand.NewSource(time.Now().UnixMicro()))
+
+// The Informer data structure implements the Gossip interface
+type Informer struct {
+ myID string
+ localSeqNumber int64
+ state *GossipState
+ committedOffsets map[string]int64
+ activeMemberIDs map[string]empty
+ otherMembers []*Member
+ consensusChecks *ConsensusChecks
+ getBlockedMembers func() set.Set[string]
+ gossipFanOut int
+ gossipMaxSend int
+ throttler actor.ShouldThrottle
+}
+
+// makes sure Informer complies with the Gossip interface
+var _ Gossip = (*Informer)(nil)
+
+// Creates a new Informer value with the given properties and returns
+// back a pointer to its memory location in the heap
+func newInformer(myID string, getBlockedMembers func() set.Set[string], fanOut int, maxSend int) *Informer {
+ informer := Informer{
+ myID: myID,
+ state: &GossipState{
+ Members: map[string]*GossipState_GossipMemberState{},
+ },
+ committedOffsets: map[string]int64{},
+ activeMemberIDs: map[string]empty{},
+ otherMembers: []*Member{},
+ consensusChecks: NewConsensusChecks(),
+ getBlockedMembers: getBlockedMembers,
+ gossipFanOut: fanOut,
+ gossipMaxSend: maxSend,
+ }
+ informer.throttler = actor.NewThrottle(3, 60*time.Second, informer.throttledLog)
+ return &informer
+}
+
+// called when there is a cluster topology update
+func (inf *Informer) UpdateClusterTopology(topology *ClusterTopology) {
+ var others []*Member
+ for _, member := range topology.Members {
+ if member.Id != inf.myID {
+ others = append(others, member)
+ }
+ }
+ inf.otherMembers = others
+
+ active := make(map[string]empty)
+ for _, member := range topology.Members {
+ active[member.Id] = empty{}
+ }
+
+ inf.SetState(TopologyKey, topology)
+}
+
+// sets new update key state using the given proto message
+func (inf *Informer) SetState(key string, message proto.Message) {
+ inf.localSeqNumber = setKey(inf.state, key, message, inf.myID, inf.localSeqNumber)
+
+ //if inf.throttler() == actor.Open {
+ // sequenceNumbers := map[string]uint64{}
+ //
+ // for _, memberState := range inf.state.Members {
+ // for key, value := range memberState.Values {
+ // sequenceNumbers[key] = uint64(value.SequenceNumber)
+ // }
+ // }
+ //
+ // // plog.Debug("Setting state", log.String("key", key), log.String("value", message.String()), log.Object("state", sequenceNumbers))
+ //}
+
+ if _, ok := inf.state.Members[inf.myID]; !ok {
+ plog.Error("State corrupt")
+ }
+
+ inf.checkConsensusKey(key)
+}
+
+// sends this informer local state to remote informers chosen randomly
+// from the slice of other members known by this informer until gossipFanOut
+// number of sent has been reached
+func (inf *Informer) SendState(sendStateToMember LocalStateSender) {
+ // inf.purgeBannedMembers() // TODO
+ for _, member := range inf.otherMembers {
+ ensureMemberStateExists(inf.state, member.Id)
+ }
+
+ // make a copy of the otherMembers so we can sort it randomly
+ otherMembers := make([]*Member, len(inf.otherMembers))
+ copy(otherMembers, inf.otherMembers)
+
+ // shuffles the order of the slice elements
+ rnd.Shuffle(len(otherMembers), func(i, j int) {
+ otherMembers[i], otherMembers[j] = otherMembers[j], otherMembers[i]
+ })
+
+ fanOutCount := 0
+ for _, member := range otherMembers {
+ memberState := inf.GetMemberStateDelta(member.Id)
+ if !memberState.HasState {
+ // nothing has change, skip it
+ continue
+ }
+
+ // fire and forget, we handle results in ReenterAfter
+ sendStateToMember(memberState, member)
+ fanOutCount++
+
+ // we reached our limit, break
+ if fanOutCount >= inf.gossipFanOut {
+ break
+ }
+ }
+}
+
+func (inf *Informer) GetMemberStateDelta(targetMemberID string) *MemberStateDelta {
+ var count int
+
+ // newState will old the final new state to be sent
+ newState := GossipState{Members: make(map[string]*GossipState_GossipMemberState)}
+
+ // hashmaps in Go are random by nature so no need to randomize state.Members
+ pendingOffsets := inf.committedOffsets
+
+ // create a new map with gossipMaxSend entries max
+ members := make(map[string]*GossipState_GossipMemberState)
+
+ // add ourselves to the gossip list if we are in the members state
+ if member, ok := inf.state.Members[inf.myID]; ok {
+ members[inf.myID] = member
+ count++
+ }
+
+ // Go hash maps are unordered by nature so we don't need to randomize them
+ // iterate over our state members skipping ourselves and add them to the
+ // local `newState` variable until gossipMaxSend is reached
+ for id, member := range inf.state.Members {
+ if id == inf.myID {
+ continue
+ }
+
+ count++
+ members[id] = member
+
+ if count > inf.gossipMaxSend {
+ break
+ }
+ }
+
+ // now we iterate over our subset of members and proceed to send them if applicable
+ for memberID, memberState := range members {
+
+ // create an empty state
+ newMemberState := GossipState_GossipMemberState{
+ Values: make(map[string]*GossipKeyValue),
+ }
+
+ watermarkKey := fmt.Sprintf("%s.%s", targetMemberID, memberID)
+
+ // get the water mark
+ watermark := inf.committedOffsets[watermarkKey]
+ newWatermark := watermark
+
+ // for each value in member state
+ for key, value := range memberState.Values {
+
+ if value.SequenceNumber <= watermark {
+ continue
+ }
+
+ if value.SequenceNumber > newWatermark {
+ newWatermark = value.SequenceNumber
+ }
+
+ newMemberState.Values[key] = value
+ }
+
+ // do not send memberStates that we have no new data for
+ if len(newMemberState.Values) > 0 {
+ newState.Members[memberID] = &newMemberState
+ pendingOffsets[watermarkKey] = newWatermark
+ }
+ }
+
+ hasState := reflect.DeepEqual(inf.committedOffsets, pendingOffsets)
+ memberState := &MemberStateDelta{
+ TargetMemberID: targetMemberID,
+ HasState: hasState,
+ State: &newState,
+ CommitOffsets: func() {
+ inf.commitPendingOffsets(pendingOffsets)
+ },
+ }
+
+ return memberState
+}
+
+// adds a new consensus checker to this informer
+func (inf *Informer) AddConsensusCheck(id string, check *ConsensusCheck) {
+ inf.consensusChecks.Add(id, check)
+
+ // check when adding, if we are already consistent
+ check.check(inf.state, inf.activeMemberIDs)
+}
+
+// removes a consensus checker from this informer
+func (inf *Informer) RemoveConsensusCheck(id string) {
+ inf.consensusChecks.Remove(id)
+}
+
+// retrieves this informer current state for the given key
+// returns map containing each known member id and their value
+func (inf *Informer) GetState(key string) map[string]*GossipKeyValue {
+ entries := make(map[string]*GossipKeyValue)
+
+ for memberID, memberState := range inf.state.Members {
+ if value, ok := memberState.Values[key]; ok {
+ entries[memberID] = value
+ }
+ }
+
+ return entries
+}
+
+// receives a remote informer state
+func (inf *Informer) ReceiveState(remoteState *GossipState) []*GossipUpdate {
+ updates, newState, updatedKeys := mergeState(inf.state, remoteState)
+ if len(updates) == 0 {
+ return nil
+ }
+
+ inf.state = newState
+ keys := make([]string, 0, len(updatedKeys))
+ for k := range updatedKeys {
+ keys = append(keys, k)
+ }
+
+ inf.CheckConsensus(keys...)
+ return updates
+}
+
+// check consensus for the given keys
+func (inf *Informer) CheckConsensus(updatedKeys ...string) {
+ for _, consensusCheck := range inf.consensusChecks.GetByUpdatedKeys(updatedKeys) {
+ consensusCheck.check(inf.state, inf.activeMemberIDs)
+ }
+}
+
+// runs checkers on key updates
+func (inf *Informer) checkConsensusKey(updatedKey string) {
+ for _, consensusCheck := range inf.consensusChecks.GetByUpdatedKey(updatedKey) {
+ consensusCheck.check(inf.state, inf.activeMemberIDs)
+ }
+}
+
+func (inf *Informer) commitPendingOffsets(offsets map[string]int64) {
+ for key, seqNumber := range offsets {
+ if offset, ok := inf.committedOffsets[key]; !ok || offset < seqNumber {
+ inf.committedOffsets[key] = seqNumber
+ }
+ }
+}
+
+func (inf *Informer) throttledLog(counter int32) {
+ plog.Debug("[Gossip] Setting State", log.Int("throttled", int(counter)))
+}
diff --git a/cluster/informer_test.go b/cluster/informer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..edbe95ac8ceab0eb4d645dc457353ebe7493e62e
--- /dev/null
+++ b/cluster/informer_test.go
@@ -0,0 +1,210 @@
+package cluster
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+
+ "github.com/asynkron/gofun/set"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+func TestInformer_SetState(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+}
+
+func TestInformer_GetState(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+
+ m := i.GetState("heartbeat")
+
+ x, ok := m["member1"]
+
+ if !ok {
+ t.Error("not ok")
+ }
+
+ var s2 MemberHeartbeat
+ err := x.Value.UnmarshalTo(&s2)
+ if err != nil {
+ t.Error("unmarshal state error")
+ }
+}
+
+func TestInformer_ReceiveState(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+ dummyValue, _ := anypb.New(s)
+
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+
+ remoteState := &GossipState{
+ Members: GossipMemberStates{
+ "member2": {
+ Values: GossipKeyValues{
+ "heartbeat": {
+ Value: dummyValue,
+ SequenceNumber: 1,
+ },
+ },
+ },
+ },
+ }
+
+ i.ReceiveState(remoteState)
+
+ m := i.GetState("heartbeat")
+
+ var ok bool
+
+ m1, ok := m["member1"]
+
+ if !ok {
+ t.Error("member1 is missing")
+ }
+
+ var s1 MemberHeartbeat
+
+ err := m1.Value.UnmarshalTo(&s1)
+ if err != nil {
+ t.Error("unmarshal member1 state error")
+ }
+
+ // ensure we see member2 after receiving state
+ m2, ok := m["member2"]
+
+ if !ok {
+ t.Error("member2 is missing")
+ }
+
+ var s2 MemberHeartbeat
+
+ err = m2.Value.UnmarshalTo(&s2)
+
+ if err != nil {
+ t.Error("unmarshal member2 state error")
+ }
+}
+
+func TestInformer_SendState(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+
+ sendState := func(memberStateDelta *MemberStateDelta, member *Member) {
+ fmt.Printf("%+v\n", memberStateDelta) //nolint:forbidigo
+ wg.Done()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+ // the cluster sees two nodes. itself and member2
+ i.UpdateClusterTopology(&ClusterTopology{
+ Members: []*Member{
+ {
+ Id: "member2",
+ Host: "member2",
+ Port: 123,
+ },
+ {
+ Id: "member1",
+ Host: "member1",
+ Port: 333,
+ },
+ },
+ })
+
+ // gossip never sends to self, so the only member we can send to is member2
+ i.SendState(sendState)
+ wg.Wait()
+}
+
+func TestInformer_UpdateClusterTopology(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+ // the cluster sees two nodes. itself and member2
+ i.UpdateClusterTopology(&ClusterTopology{
+ Members: []*Member{
+ {
+ Id: "member2",
+ Host: "member2",
+ Port: 123,
+ },
+ {
+ Id: "member1",
+ Host: "member1",
+ Port: 333,
+ },
+ },
+ })
+
+ // TODO: how do we check that the cluster topology was updated?
+}
+
+func TestInformer_GetMemberStateDelta(t *testing.T) {
+ t.Parallel()
+
+ a := func() set.Set[string] {
+ return set.New[string]()
+ }
+
+ s := &MemberHeartbeat{
+ ActorStatistics: &ActorStatistics{},
+ }
+
+ i := newInformer("member1", a, 3, 3)
+ i.SetState("heartbeat", s)
+
+ m := i.GetMemberStateDelta("member1")
+
+ if m == nil {
+ t.Error("member state delta is nil")
+ }
+}
diff --git a/cluster/key_value_store.go b/cluster/key_value_store.go
new file mode 100644
index 0000000000000000000000000000000000000000..e342b1181af3b278155b44a0be32581a6b1b5b72
--- /dev/null
+++ b/cluster/key_value_store.go
@@ -0,0 +1,25 @@
+package cluster
+
+import "golang.org/x/net/context"
+
+// KeyValueStore is a distributed key value store
+type KeyValueStore[T any] interface {
+ // Set the value for the given key.
+ Set(ctx context.Context, key string, value T) error
+ // Get the value for the given key..
+ Get(ctx context.Context, key string) (T, error)
+ // Clear the value for the given key.
+ Clear(ctx context.Context, key string) error
+}
+
+// EmptyKeyValueStore is a key value store that does nothing.
+type EmptyKeyValueStore[T any] struct{}
+
+func (e *EmptyKeyValueStore[T]) Set(_ context.Context, _ string, _ T) error { return nil }
+
+func (e *EmptyKeyValueStore[T]) Get(_ context.Context, _ string) (T, error) {
+ var r T
+ return r, nil
+}
+
+func (e *EmptyKeyValueStore[T]) Clear(_ context.Context, _ string) error { return nil }
diff --git a/cluster/kind.go b/cluster/kind.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ba59be116efd17b51d25ef37bce459901dbfdc9
--- /dev/null
+++ b/cluster/kind.go
@@ -0,0 +1,57 @@
+package cluster
+
+import (
+ "sync/atomic"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Kind represents the kinds of actors a cluster can manage
+type Kind struct {
+ Kind string
+ Props *actor.Props
+ StrategyBuilder func(*Cluster) MemberStrategy
+}
+
+// NewKind creates a new instance of a kind
+func NewKind(kind string, props *actor.Props) *Kind {
+ // add cluster middleware
+ p := props.Clone(withClusterReceiveMiddleware())
+ return &Kind{
+ Kind: kind,
+ Props: p,
+ StrategyBuilder: nil,
+ }
+}
+
+func (k *Kind) WithMemberStrategy(strategyBuilder func(*Cluster) MemberStrategy) {
+ k.StrategyBuilder = strategyBuilder
+}
+
+func (k *Kind) Build(cluster *Cluster) *ActivatedKind {
+ var strategy MemberStrategy = nil
+ if k.StrategyBuilder != nil {
+ strategy = k.StrategyBuilder(cluster)
+ }
+
+ return &ActivatedKind{
+ Kind: k.Kind,
+ Props: k.Props,
+ Strategy: strategy,
+ }
+}
+
+type ActivatedKind struct {
+ Kind string
+ Props *actor.Props
+ Strategy MemberStrategy
+ count int32
+}
+
+func (ak *ActivatedKind) Inc() {
+ atomic.AddInt32(&ak.count, 1)
+}
+
+func (ak *ActivatedKind) Dev() {
+ atomic.AddInt32(&ak.count, -1)
+}
diff --git a/cluster/log.go b/cluster/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..cff25db22ae8afcafcea60931d80a902edaf5126
--- /dev/null
+++ b/cluster/log.go
@@ -0,0 +1,14 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+var plog = log.New(log.DefaultLevel, "[CLUSTER]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/cluster/member.go b/cluster/member.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6f586405120ce53a2b09a5e4d3f6d30aacd7afb
--- /dev/null
+++ b/cluster/member.go
@@ -0,0 +1,80 @@
+package cluster
+
+import (
+ "sort"
+ "strconv"
+ "strings"
+
+ murmur32 "github.com/twmb/murmur3"
+)
+
+type Members []*Member
+
+func (m *Members) ToSet() *MemberSet {
+ return NewMemberSet(*m)
+}
+
+func (m *Member) HasKind(kind string) bool {
+ for _, k := range m.Kinds {
+ if k == kind {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Address return a "host:port".
+// Member defined by protos.proto
+func (m *Member) Address() string {
+ return m.Host + ":" + strconv.FormatInt(int64(m.Port), 10)
+}
+
+func TopologyHash(members Members) uint64 {
+ // C# version
+ // var x = membersByMemberId.Select(m => m.Id).OrderBy(i => i).ToArray();
+ // var key = string.Join("", x);
+ // var hashBytes = MurmurHash2.Hash(key);
+ // return hashBytes;
+
+ sort.Slice(members, func(i, j int) bool {
+ return members[i].Id < members[j].Id
+ })
+
+ // I assume this is not the fastest way to do this?
+ s := ""
+ for _, m := range members {
+ s += m.Id
+ }
+
+ // TODO: this HAS to be compatible with the same hashBytes in .NET
+ // add plenty of tests
+ hash := murmur32.Sum64([]byte(s))
+
+ return hash
+}
+
+func MembersToMap(members Members) map[string]*Member {
+ mapp := make(map[string]*Member)
+ for _, m := range members {
+ mapp[m.Id] = m
+ }
+
+ return mapp
+}
+
+func SortMembers(members Members) {
+ sort.Slice(members, func(i, j int) bool {
+ addrI := members[i].Id
+ addrJ := members[j].Id
+
+ return strings.Compare(addrI, addrJ) > 0
+ })
+}
+
+func CopySortMembers(members Members) Members {
+ tmp := append(make(Members, 0, len(members)), members...)
+ SortMembers(tmp)
+
+ return tmp
+}
diff --git a/cluster/member_list.go b/cluster/member_list.go
new file mode 100644
index 0000000000000000000000000000000000000000..4acbb943d373402fd9f539bb807991e27160355c
--- /dev/null
+++ b/cluster/member_list.go
@@ -0,0 +1,259 @@
+package cluster
+
+import (
+ "context"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+ "google.golang.org/protobuf/types/known/anypb"
+)
+
+// MemberList is responsible to keep track of the current cluster topology
+// it does so by listening to changes from the ClusterProvider.
+// the default ClusterProvider is consul.ConsulProvider which uses the Consul HTTP API to scan for changes
+type MemberList struct {
+ cluster *Cluster
+ mutex sync.RWMutex
+ members *MemberSet
+ memberStrategyByKind map[string]MemberStrategy
+
+ eventSteam *eventstream.EventStream
+ topologyConsensus ConsensusHandler
+}
+
+func NewMemberList(cluster *Cluster) *MemberList {
+ memberList := &MemberList{
+ cluster: cluster,
+ members: emptyMemberSet,
+ memberStrategyByKind: make(map[string]MemberStrategy),
+ eventSteam: cluster.ActorSystem.EventStream,
+ }
+ memberList.eventSteam.Subscribe(func(evt interface{}) {
+ switch t := evt.(type) {
+ case *GossipUpdate:
+ if t.Key != "topology" {
+ break
+ }
+
+ // get blocked members from all other member states
+ // and merge that without own blocked set
+ var topology ClusterTopology
+ if err := t.Value.UnmarshalTo(&topology); err != nil {
+ plog.Warn("could not unpack into ClusterTopology proto.Message form Any", log.Error(err))
+
+ break
+ }
+ blocked := topology.Blocked
+ memberList.cluster.Remote.BlockList().Block(blocked...)
+ }
+ })
+
+ return memberList
+}
+
+func (ml *MemberList) stopMemberList() {
+ // ml.cluster.ActorSystem.EventStream.Unsubscribe(ml.membershipSub)
+}
+
+func (ml *MemberList) InitializeTopologyConsensus() {
+ ml.topologyConsensus = ml.cluster.Gossip.RegisterConsensusCheck("topology", func(any *anypb.Any) interface{} {
+ var topology ClusterTopology
+ if unpackErr := any.UnmarshalTo(&topology); unpackErr != nil {
+ plog.Error("could not unpack topology message", log.Error(unpackErr))
+
+ return nil
+ }
+
+ return topology.TopologyHash
+ })
+}
+
+func (ml *MemberList) TopologyConsensus(ctx context.Context) (uint64, bool) {
+ result, ok := ml.topologyConsensus.TryGetConsensus(ctx)
+ if ok {
+ res, _ := result.(uint64)
+
+ return res, true
+ }
+
+ return 0, false
+}
+
+func (ml *MemberList) getPartitionMember(name, kind string) string {
+ ml.mutex.RLock()
+ defer ml.mutex.RUnlock()
+
+ var res string
+ if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
+ res = memberStrategy.GetPartition(name)
+ }
+
+ return res
+}
+
+func (ml *MemberList) getPartitionMemberV2(clusterIdentity *ClusterIdentity) string {
+ ml.mutex.RLock()
+ defer ml.mutex.RUnlock()
+
+ if ms, ok := ml.memberStrategyByKind[clusterIdentity.Kind]; ok {
+ return ms.GetPartition(clusterIdentity.Identity)
+ }
+
+ return ""
+}
+
+func (ml *MemberList) GetActivatorMember(kind string, requestSourceAddress string) string {
+ ml.mutex.RLock()
+ defer ml.mutex.RUnlock()
+
+ var res string
+ if memberStrategy, ok := ml.memberStrategyByKind[kind]; ok {
+ res = memberStrategy.GetActivator(requestSourceAddress)
+ }
+
+ return res
+}
+
+func (ml *MemberList) Length() int {
+ return ml.members.Len()
+}
+
+func (ml *MemberList) Members() *MemberSet {
+ return ml.members
+}
+
+func (ml *MemberList) UpdateClusterTopology(members Members) {
+ ml.mutex.Lock()
+ defer ml.mutex.Unlock()
+
+ // TLDR:
+ // this method basically filters out any member status in the blocked list
+ // then makes a delta between new and old members
+ // notifying the cluster accordingly which members left or joined
+
+ topology, done, active, joined, left := ml.getTopologyChanges(members)
+ if done {
+ return
+ }
+
+ // include any new blocked members into the known set of blocked members
+ for _, m := range left.Members() {
+ ml.cluster.Remote.BlockList().Block(m.Id)
+ }
+
+ ml.members = active
+
+ // notify that these members left
+ for _, m := range left.Members() {
+ ml.memberLeave(m)
+ ml.TerminateMember(m)
+ }
+
+ // notify that these members joined
+ for _, m := range joined.Members() {
+ ml.memberJoin(m)
+ }
+
+ ml.cluster.ActorSystem.EventStream.Publish(topology)
+
+ plog.Info("Updated ClusterTopology",
+ log.Uint64("topology-hash", topology.TopologyHash),
+ log.Int("members", len(topology.Members)),
+ log.Int("joined", len(topology.Joined)),
+ log.Int("left", len(topology.Left)),
+ log.Int("blocked", len(topology.Blocked)),
+ log.Int("membersFromProvider", len(members)))
+}
+
+func (ml *MemberList) memberJoin(joiningMember *Member) {
+ plog.Info("member joined", log.String("member", joiningMember.Id))
+
+ for _, kind := range joiningMember.Kinds {
+ if ml.memberStrategyByKind[kind] == nil {
+ ml.memberStrategyByKind[kind] = ml.getMemberStrategyByKind(kind)
+ }
+
+ ml.memberStrategyByKind[kind].AddMember(joiningMember)
+ }
+}
+
+func (ml *MemberList) memberLeave(leavingMember *Member) {
+ for _, kind := range leavingMember.Kinds {
+ if ml.memberStrategyByKind[kind] == nil {
+ continue
+ }
+
+ ml.memberStrategyByKind[kind].RemoveMember(leavingMember)
+ }
+}
+
+func (ml *MemberList) getTopologyChanges(members Members) (topology *ClusterTopology, unchanged bool, active *MemberSet, joined *MemberSet, left *MemberSet) {
+ memberSet := NewMemberSet(members)
+
+ // get active members
+ // (this bit means that we will never allow a member that failed a health check to join back in)
+ blocked := ml.cluster.GetBlockedMembers().ToSlice()
+
+ active = memberSet.ExceptIds(blocked)
+
+ // nothing changed? exit
+ if active.Equals(ml.members) {
+ return nil, true, nil, nil, nil
+ }
+
+ left = ml.members.Except(active)
+ joined = active.Except(ml.members)
+
+ topology = &ClusterTopology{
+ TopologyHash: active.TopologyHash(),
+ Members: active.Members(),
+ Left: left.Members(),
+ Joined: joined.Members(),
+ }
+
+ return topology, false, active, joined, left
+}
+
+func (ml *MemberList) TerminateMember(m *Member) {
+ // tell the world that this endpoint should is no longer relevant
+ ml.cluster.ActorSystem.EventStream.Publish(&remote.EndpointTerminatedEvent{
+ Address: m.Address(),
+ })
+}
+
+func (ml *MemberList) BroadcastEvent(message interface{}, includeSelf bool) {
+ for _, m := range ml.members.members {
+ if !includeSelf && m.Id == ml.cluster.ActorSystem.ID {
+ continue
+ }
+
+ pid := actor.NewPID(m.Address(), "eventstream")
+ ml.cluster.ActorSystem.Root.Send(pid, message)
+ }
+}
+
+func (ml *MemberList) ContainsMemberID(memberID string) bool {
+ return ml.members.ContainsID(memberID)
+}
+
+func (ml *MemberList) getMemberStrategyByKind(kind string) MemberStrategy {
+ plog.Info("creating member strategy", log.String("kind", kind))
+
+ clusterKind, ok := ml.cluster.TryGetClusterKind(kind)
+
+ if ok {
+ if clusterKind.Strategy != nil {
+ return clusterKind.Strategy
+ }
+ }
+
+ strategy := ml.cluster.Config.MemberStrategyBuilder(ml.cluster, kind)
+ if strategy != nil {
+ return strategy
+ }
+
+ return newDefaultMemberStrategy(ml.cluster, kind)
+}
diff --git a/cluster/member_list_test.go b/cluster/member_list_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7170d2114d9a6010573e42b39518864768e6eb4d
--- /dev/null
+++ b/cluster/member_list_test.go
@@ -0,0 +1,286 @@
+package cluster
+
+import (
+ "fmt"
+ "sort"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+//func TestPublishRaceCondition(t *testing.T) {
+// actorSystem := actor.NewActorSystem()
+// c := New(actorSystem, Configure("mycluster", nil, nil, remote.Configure("127.0.0.1", 0)))
+// NewMemberList(c)
+// rounds := 1000
+//
+// var wg sync.WaitGroup
+// wg.Add(2 * rounds)
+//
+// go func() {
+// for i := 0; i < rounds; i++ {
+// actorSystem.EventStream.Publish(TopologyEvent(Members{{}, {}}))
+// actorSystem.EventStream.Publish(TopologyEvent(Members{{}}))
+// wg.Done()
+// }
+// }()
+//
+// go func() {
+// for i := 0; i < rounds; i++ {
+// s := actorSystem.EventStream.Subscribe(func(evt interface{}) {})
+// actorSystem.EventStream.Unsubscribe(s)
+// wg.Done()
+// }
+// }()
+//
+// if waitTimeout(&wg, 2*time.Second) {
+// t.Error("Should not run into a timeout")
+// }
+//}
+
+// https://stackoverflow.com/questions/32840687/timeout-for-waitgroup-wait
+func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
+ c := make(chan struct{})
+ go func() {
+ defer close(c)
+ wg.Wait()
+ }()
+ select {
+ case <-c:
+ return false // completed normally
+ case <-time.After(timeout):
+ return true // timed out
+ }
+}
+
+func TestMemberList_UpdateClusterTopology(t *testing.T) {
+ c := newClusterForTest("test-UpdateClusterTopology", nil)
+ obj := NewMemberList(c)
+ empty := make([]*Member, 0)
+
+ t.Run("init", func(t *testing.T) {
+ assert := assert.New(t)
+ members := newMembersForTest(2)
+ changes, unchanged, actives, _, _ := obj.getTopologyChanges(members)
+ assert.False(unchanged)
+ expected := &ClusterTopology{TopologyHash: TopologyHash(members), Members: members, Joined: members, Left: empty}
+ assert.Equal(expected.TopologyHash, changes.TopologyHash)
+
+ var m1, m2 *MemberSet
+ m1 = NewMemberSet(expected.Members)
+ m2 = NewMemberSet(changes.Members)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Joined)
+ m2 = NewMemberSet(changes.Joined)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Left)
+ m2 = NewMemberSet(changes.Left)
+ assert.Equal(m1, m2)
+
+ // current members
+ obj.members = actives
+ })
+
+ t.Run("join", func(t *testing.T) {
+ assert := assert.New(t)
+ assert.Equal(2, obj.members.Len())
+ members := newMembersForTest(4)
+ changes, unchanged, actives, _, _ := obj.getTopologyChanges(members)
+ assert.False(unchanged)
+ // _sorted(changes)
+ expected := &ClusterTopology{TopologyHash: TopologyHash(members), Members: members, Joined: members[2:4], Left: empty}
+ assert.Equal(expected.TopologyHash, changes.TopologyHash)
+
+ var m1, m2 *MemberSet
+ m1 = NewMemberSet(expected.Members)
+ m2 = NewMemberSet(changes.Members)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Joined)
+ m2 = NewMemberSet(changes.Joined)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Left)
+ m2 = NewMemberSet(changes.Left)
+ assert.Equal(m1, m2)
+
+ obj.members = actives
+ })
+
+ t.Run("left", func(t *testing.T) {
+ assert := assert.New(t)
+ assert.Equal(4, obj.members.Len())
+ members := newMembersForTest(4)
+ changes, _, _, _, _ := obj.getTopologyChanges(members[2:4])
+ expected := &ClusterTopology{TopologyHash: TopologyHash(members[2:4]), Members: members[2:4], Joined: empty, Left: members[0:2]}
+ assert.Equal(expected.TopologyHash, changes.TopologyHash)
+
+ var m1, m2 *MemberSet
+ m1 = NewMemberSet(expected.Members)
+ m2 = NewMemberSet(changes.Members)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Joined)
+ m2 = NewMemberSet(changes.Joined)
+ assert.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Left)
+ m2 = NewMemberSet(changes.Left)
+ assert.Equal(m1, m2)
+ })
+}
+
+func newMembersForTest(count int, kinds ...string) Members {
+ if len(kinds) == 0 {
+ kinds = append(kinds, "kind")
+ }
+ members := make(Members, count)
+ for i := 0; i < count; i++ {
+ members[i] = &Member{
+ Id: fmt.Sprintf("memberId-%d", i),
+ Host: "127.0.0.1",
+ Port: int32(i),
+ Kinds: kinds,
+ }
+ }
+ return members
+}
+
+func TestMemberList_UpdateClusterTopology2(t *testing.T) {
+ c := newClusterForTest("test-UpdateClusterTopology", nil)
+
+ obj := NewMemberList(c)
+ dumpMembers := func(list Members) {
+ t.Logf("membersByMemberId=%d", len(list))
+
+ for _, m := range list {
+ t.Logf("\t%s", m.Address())
+ }
+ }
+
+ empty := make([]*Member, 0)
+
+ _ = dumpMembers
+ _sorted := func(tpl *ClusterTopology) {
+ _sortMembers := func(list Members) {
+ sort.Slice(list, func(i, j int) bool {
+ return (list)[i].Port < (list)[j].Port
+ })
+ }
+ _sortMembers(tpl.Members)
+ _sortMembers(tpl.Left)
+ _sortMembers(tpl.Joined)
+ }
+
+ a := assert.New(t)
+ members := newMembersForTest(2)
+ changes, _, _, _, _ := obj.getTopologyChanges(members) //nolint:dogsled
+ _sorted(changes)
+
+ expected := &ClusterTopology{TopologyHash: TopologyHash(members), Members: members, Joined: members, Left: empty}
+
+ a.Equal(expected.TopologyHash, changes.TopologyHash)
+
+ var m1, m2 *MemberSet
+ m1 = NewMemberSet(expected.Members)
+ m2 = NewMemberSet(changes.Members)
+ a.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Joined)
+ m2 = NewMemberSet(changes.Joined)
+ a.Equal(m1, m2)
+
+ m1 = NewMemberSet(expected.Left)
+ m2 = NewMemberSet(changes.Left)
+ a.Equal(m1, m2)
+}
+
+func TestMemberList_getPartitionMember(t *testing.T) {
+ t.Parallel()
+
+ c := newClusterForTest("test-memberlist", nil)
+ obj := NewMemberList(c)
+
+ for _, v := range []int{1, 2, 10, 100, 1000} {
+ members := newMembersForTest(v)
+ obj.UpdateClusterTopology(members)
+
+ testName := fmt.Sprintf("member*%d", v)
+ t.Run(testName, func(t *testing.T) {
+ //assert := assert.New(t)
+ //
+ //identity := NewClusterIdentity("name", "kind")
+ //// address := obj.getPartitionMemberV2(identity)
+ //// assert.NotEmpty(address)
+ //
+ //identity = NewClusterIdentity("name", "nonkind")
+ //// address = obj.getPartitionMemberV2(identity)
+ //// assert.Empty(address)
+ })
+ }
+}
+
+//func BenchmarkMemberList_getPartitionMemberV2(b *testing.B) {
+// SetLogLevel(log.ErrorLevel)
+// actorSystem := actor.NewActorSystem()
+// c := New(actorSystem, Configure("mycluster", nil, nil, remote.Configure("127.0.0.1", 0)))
+// obj := NewMemberList(c)
+// for i, v := range []int{1, 2, 3, 5, 10, 100, 1000, 2000} {
+// members := _newTopologyEventForTest(v)
+// obj.UpdateClusterTopology(members)
+// testName := fmt.Sprintf("member*%d", v)
+// runtime.GC()
+//
+// identity := &ClusterIdentity{Identity: fmt.Sprintf("name-%d", rand.Int()), Kind: "kind"}
+// b.Run(testName, func(b *testing.B) {
+// for i := 0; i < b.N; i++ {
+// address := obj.getPartitionMemberV2(identity)
+// if address == "" {
+// b.Fatalf("empty address membersByMemberId=%d", v)
+// }
+// }
+// })
+// }
+//}
+
+//func TestMemberList_getPartitionMemberV2(t *testing.T) {
+// assert := assert.New(t)
+//
+// tplg := _newTopologyEventForTest(10)
+// c := _newClusterForTest("test-memberlist")
+// obj := NewMemberList(c)
+// obj.UpdateClusterTopology(tplg, 1)
+//
+// assert.Contains(obj.memberStrategyByKind, "kind")
+// addr := obj.getPartitionMemberV2(&ClusterIdentity{Kind: "kind", Identity: "name"})
+// assert.NotEmpty(addr)
+//
+// // consistent
+// for i := 0; i < 10; i++ {
+// addr2 := obj.getPartitionMemberV2(&ClusterIdentity{Kind: "kind", Identity: "name"})
+// assert.NotEmpty(addr2)
+// assert.Equal(addr, addr2)
+// }
+//}
+
+func TestMemberList_newMemberStrategies(t *testing.T) {
+ t.Parallel()
+ a := assert.New(t)
+
+ c := newClusterForTest("test-memberlist", nil)
+ obj := NewMemberList(c)
+
+ for _, v := range []int{1, 10, 100, 1000} {
+ members := newMembersForTest(v, "kind1", "kind2")
+ obj.UpdateClusterTopology(members)
+ a.Equal(2, len(obj.memberStrategyByKind))
+ a.Contains(obj.memberStrategyByKind, "kind1")
+
+ a.Equal(v, len(obj.memberStrategyByKind["kind1"].GetAllMembers()))
+ a.Equal(v, len(obj.memberStrategyByKind["kind2"].GetAllMembers()))
+ }
+}
diff --git a/cluster/member_state_delta.go b/cluster/member_state_delta.go
new file mode 100644
index 0000000000000000000000000000000000000000..01dd405a623bbc79a8e727b96de8b028e936673c
--- /dev/null
+++ b/cluster/member_state_delta.go
@@ -0,0 +1,10 @@
+// Copyright (C) 2015-2022 Asynkron AB All rights reserved
+
+package cluster
+
+type MemberStateDelta struct {
+ TargetMemberID string
+ HasState bool
+ State *GossipState
+ CommitOffsets func()
+}
diff --git a/cluster/member_status.go b/cluster/member_status.go
new file mode 100644
index 0000000000000000000000000000000000000000..048e0dac093afcba6e69834ce5b9a607b58f46cb
--- /dev/null
+++ b/cluster/member_status.go
@@ -0,0 +1,11 @@
+package cluster
+
+type MemberStatus struct {
+ Member
+ MemberID string // for compatibility
+ Alive bool
+}
+
+func (m *MemberStatus) Address() string {
+ return m.Member.Address()
+}
diff --git a/cluster/member_status_events.go b/cluster/member_status_events.go
new file mode 100644
index 0000000000000000000000000000000000000000..e67caa978b628e4f30a402f9ed5d9a75ef107923
--- /dev/null
+++ b/cluster/member_status_events.go
@@ -0,0 +1,52 @@
+package cluster
+
+import "fmt"
+
+type MemberStatusEvent interface {
+ MemberStatusEvent()
+ GetKinds() []string
+}
+
+type MemberMeta struct {
+ Host string
+ Port int
+ Kinds []string
+}
+
+func (e *MemberMeta) Name() string {
+ return fmt.Sprintf("%v:%v", e.Host, e.Port)
+}
+
+func (e *MemberMeta) GetKinds() []string {
+ return e.Kinds
+}
+
+type MemberJoinedEvent struct {
+ MemberMeta
+}
+
+func (*MemberJoinedEvent) MemberStatusEvent() {}
+
+type MemberRejoinedEvent struct {
+ MemberMeta
+}
+
+func (*MemberRejoinedEvent) MemberStatusEvent() {}
+
+type MemberLeftEvent struct {
+ MemberMeta
+}
+
+func (*MemberLeftEvent) MemberStatusEvent() {}
+
+type MemberUnavailableEvent struct {
+ MemberMeta
+}
+
+func (*MemberUnavailableEvent) MemberStatusEvent() {}
+
+type MemberAvailableEvent struct {
+ MemberMeta
+}
+
+func (*MemberAvailableEvent) MemberStatusEvent() {}
diff --git a/cluster/member_strategy.go b/cluster/member_strategy.go
new file mode 100644
index 0000000000000000000000000000000000000000..d35ee3c3669c321849ec462d902d657567d5c4d0
--- /dev/null
+++ b/cluster/member_strategy.go
@@ -0,0 +1,58 @@
+package cluster
+
+type MemberStrategy interface {
+ GetAllMembers() Members
+ AddMember(member *Member)
+ RemoveMember(member *Member)
+ GetPartition(key string) string
+ GetActivator(senderAddress string) string
+}
+
+type simpleMemberStrategy struct {
+ members Members
+ rr *SimpleRoundRobin
+ rdv *Rendezvous
+}
+
+func newDefaultMemberStrategy(cluster *Cluster, kind string) MemberStrategy {
+ ms := &simpleMemberStrategy{members: make(Members, 0)}
+ ms.rr = NewSimpleRoundRobin(MemberStrategy(ms))
+ ms.rdv = NewRendezvous()
+ return ms
+}
+
+func (m *simpleMemberStrategy) AddMember(member *Member) {
+ m.members = append(m.members, member)
+ m.rdv.UpdateMembers(m.members)
+}
+
+func (m *simpleMemberStrategy) UpdateMember(member *Member) {
+ for i, mb := range m.members {
+ if mb.Address() == member.Address() {
+ m.members[i] = member
+ return
+ }
+ }
+}
+
+func (m *simpleMemberStrategy) RemoveMember(member *Member) {
+ for i, mb := range m.members {
+ if mb.Address() == member.Address() {
+ m.members = append(m.members[:i], m.members[i+1:]...)
+ m.rdv.UpdateMembers(m.members)
+ return
+ }
+ }
+}
+
+func (m *simpleMemberStrategy) GetAllMembers() Members {
+ return m.members
+}
+
+func (m *simpleMemberStrategy) GetPartition(key string) string {
+ return m.rdv.GetByIdentity(key)
+}
+
+func (m *simpleMemberStrategy) GetActivator(senderAddress string) string {
+ return m.rr.GetByRoundRobin()
+}
diff --git a/cluster/members.go b/cluster/members.go
new file mode 100644
index 0000000000000000000000000000000000000000..0a82186e3c441ab31aeba7b6c0b00cd53dbcb5b6
--- /dev/null
+++ b/cluster/members.go
@@ -0,0 +1,99 @@
+package cluster
+
+import "github.com/asynkron/gofun/set"
+
+type MemberSet struct {
+ topologyHash uint64
+ members Members
+ lookup map[string]*Member
+}
+
+var emptyMemberSet = NewMemberSet(make(Members, 0))
+
+func NewMemberSet(members Members) *MemberSet {
+ members = CopySortMembers(members)
+ lookup := MembersToMap(members)
+ ms := &MemberSet{
+ topologyHash: TopologyHash(members),
+ members: members,
+ lookup: lookup,
+ }
+
+ return ms
+}
+
+func (ms *MemberSet) Len() int {
+ return len(ms.members)
+}
+
+func (ms *MemberSet) TopologyHash() uint64 {
+ return ms.topologyHash
+}
+
+func (ms *MemberSet) Members() Members {
+ return ms.members
+}
+
+func (ms *MemberSet) ContainsID(id string) bool {
+ _, ok := ms.lookup[id]
+
+ return ok
+}
+
+func (ms *MemberSet) GetMemberById(id string) *Member {
+ member, _ := ms.lookup[id]
+
+ return member
+}
+
+func (ms *MemberSet) Except(other *MemberSet) *MemberSet {
+ res := make(Members, 0)
+
+ for _, m := range ms.members {
+ if other.ContainsID(m.Id) {
+ continue
+ }
+
+ res = append(res, m)
+ }
+
+ return NewMemberSet(res)
+}
+
+func (ms *MemberSet) ExceptIds(ids []string) *MemberSet {
+ other := set.New(ids...)
+ res := make(Members, 0)
+
+ for _, m := range ms.members {
+ if other.Contains(m.Id) {
+ continue
+ }
+
+ res = append(res, m)
+ }
+
+ return NewMemberSet(res)
+}
+
+func (ms *MemberSet) Union(other *MemberSet) *MemberSet {
+ mapp := make(map[string]*Member, 0)
+ for _, m := range ms.members {
+ mapp[m.Id] = m
+ }
+
+ for _, m := range other.members {
+ mapp[m.Id] = m
+ }
+
+ res := make(Members, 0)
+
+ for _, m := range mapp {
+ res = append(res, m)
+ }
+
+ return NewMemberSet(res)
+}
+
+func (ms *MemberSet) Equals(other *MemberSet) bool {
+ return ms.topologyHash == other.topologyHash
+}
diff --git a/cluster/messages.go b/cluster/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c67d7eba57396c983fb00bec2f2fc7dd088c1c9
--- /dev/null
+++ b/cluster/messages.go
@@ -0,0 +1,75 @@
+package cluster
+
+import (
+ "google.golang.org/protobuf/proto"
+)
+
+// Used to query the GossipActor about a given key status
+type GetGossipStateRequest struct {
+ Key string
+}
+
+// Create a new GetGossipStateRequest value and return it back
+func NewGetGossipStateRequest(key string) GetGossipStateRequest {
+ request := GetGossipStateRequest{Key: key}
+ return request
+}
+
+// Used by the GossipActor to send back the status value of a given key
+type GetGossipStateResponse struct {
+ State map[string]*GossipKeyValue
+}
+
+func NewGetGossipStateResponse(state map[string]*GossipKeyValue) GetGossipStateResponse {
+ value := GetGossipStateResponse{
+ State: state,
+ }
+ return value
+}
+
+// Used to setup Gossip Status Keys in the GossipActor
+type SetGossipStateKey struct {
+ Key string
+ Value proto.Message
+}
+
+// Create a new SetGossipStateKey value with the given data and return it back
+func NewGossipStateKey(key string, value proto.Message) SetGossipStateKey {
+ statusKey := SetGossipStateKey{
+ Key: key,
+ Value: value,
+ }
+ return statusKey
+}
+
+type SendGossipStateRequest struct{}
+
+type SendGossipStateResponse struct{}
+
+// Used by the GossipActor to respond SetGossipStatus requests
+type SetGossipStateResponse struct{}
+
+type AddConsensusCheck struct {
+ ID string
+ Check *ConsensusCheck
+}
+
+// Mimic .NET ReenterAfterCancellation on GossipActor
+type RemoveConsensusCheck struct {
+ ID string
+}
+
+func NewAddConsensusCheck(id string, check *ConsensusCheck) AddConsensusCheck {
+ value := AddConsensusCheck{
+ ID: id,
+ Check: check,
+ }
+ return value
+}
+
+func NewRemoveConsensusCheck(id string) RemoveConsensusCheck {
+ value := RemoveConsensusCheck{
+ ID: id,
+ }
+ return value
+}
diff --git a/cluster/options.go b/cluster/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..50fcae4e6ff2272ee4b267f8e2313002fd1a865e
--- /dev/null
+++ b/cluster/options.go
@@ -0,0 +1,3 @@
+package cluster
+
+type Option func(g *Gossiper)
diff --git a/cluster/pid_cache.go b/cluster/pid_cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..e5af267f1143b7c0090876ba178c000111d31b64
--- /dev/null
+++ b/cluster/pid_cache.go
@@ -0,0 +1,68 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ cmap "github.com/orcaman/concurrent-map"
+)
+
+type PidCacheValue struct {
+ cache cmap.ConcurrentMap
+}
+
+func NewPidCache() *PidCacheValue {
+ pidCache := &PidCacheValue{
+ cache: cmap.New(),
+ }
+
+ return pidCache
+}
+
+func key(identity string, kind string) string {
+ return identity + "." + kind
+}
+
+func (c *PidCacheValue) Get(identity string, kind string) (*actor.PID, bool) {
+ k := key(identity, kind)
+ v, ok := c.cache.Get(k)
+
+ if !ok {
+ return nil, false
+ }
+
+ return v.(*actor.PID), true
+}
+
+func (c *PidCacheValue) Set(identity string, kind string, pid *actor.PID) {
+ k := key(identity, kind)
+ c.cache.Set(k, pid)
+}
+
+func (c *PidCacheValue) RemoveByValue(identity string, kind string, pid *actor.PID) {
+ k := key(identity, kind)
+
+ c.cache.RemoveCb(k, func(key string, v interface{}, exists bool) bool {
+ if !exists {
+ return false
+ }
+
+ existing, _ := v.(*actor.PID)
+
+ return existing.Equal(pid)
+ })
+}
+
+func (c *PidCacheValue) Remove(identity string, kind string) {
+ k := key(identity, kind)
+ c.cache.Remove(k)
+}
+
+func (c *PidCacheValue) RemoveByMember(member *Member) {
+ addr := member.Address()
+
+ for item := range c.cache.IterBuffered() {
+ pid, _ := item.Val.(*actor.PID)
+ if pid.Address == addr {
+ c.cache.Remove(item.Key)
+ }
+ }
+}
diff --git a/cluster/pid_cache_test.go b/cluster/pid_cache_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..176fd29be8ff7614e5a54959eb27e4936ff30efa
--- /dev/null
+++ b/cluster/pid_cache_test.go
@@ -0,0 +1,89 @@
+package cluster
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+func TestPidCacheValue_Set_Test(t *testing.T) {
+ t.Parallel()
+
+ pidCache := NewPidCache()
+ pid := actor.NewPID("abc", "def")
+ pidCache.Set("abc", "k", pid)
+ res, _ := pidCache.Get("abc", "k")
+
+ if !res.Equal(pid) {
+ t.Errorf("Expected %v, got %v", pid, res)
+ }
+}
+
+func TestPidCacheValue_Get(t *testing.T) {
+ t.Parallel()
+
+ pidCache := NewPidCache()
+ pid := actor.NewPID("abc", "def")
+ pidCache.Set("abc", "k", pid)
+ res, _ := pidCache.Get("abc", "k")
+
+ if !res.Equal(pid) {
+ t.Errorf("Expected %v, got %v", pid, res)
+ }
+}
+
+func TestPidCacheValue_Remove(t *testing.T) {
+ t.Parallel()
+
+ pidCache := NewPidCache()
+ pid := actor.NewPID("abc", "def")
+ pidCache.Set("abc", "k", pid)
+ pidCache.Remove("abc", "k")
+ res, _ := pidCache.Get("abc", "k")
+
+ if res != nil {
+ t.Errorf("Expected nil, got %v", res)
+ }
+}
+
+func TestPidCacheValue_RemoveByMember(t *testing.T) {
+ t.Parallel()
+
+ member := &Member{
+ Host: "abc",
+ Port: 123,
+ }
+
+ pidCache := NewPidCache()
+ pid := actor.NewPID("abc:123", "def")
+ pidCache.Set("abc", "k", pid)
+ pidCache.RemoveByMember(member)
+ res, _ := pidCache.Get("abc", "k")
+
+ if res != nil {
+ t.Errorf("Expected nil, got %v", res)
+ }
+}
+
+func TestPidCacheValue_RemoveByValue(t *testing.T) {
+ t.Parallel()
+
+ pidCache := NewPidCache()
+ pid := actor.NewPID("abc", "def1234")
+ pid2 := actor.NewPID("abc", "def3532534")
+
+ pidCache.Set("abc", "k", pid)
+ pidCache.RemoveByValue("abc", "k", pid2)
+ res, _ := pidCache.Get("abc", "k")
+
+ if res == nil {
+ t.Errorf("Expected %v, got %v", pid, res)
+ }
+
+ pidCache.RemoveByValue("abc", "k", pid)
+ res, _ = pidCache.Get("abc", "k")
+
+ if res != nil {
+ t.Errorf("Expected nil, got %v", res)
+ }
+}
diff --git a/cluster/pubsub.go b/cluster/pubsub.go
new file mode 100644
index 0000000000000000000000000000000000000000..97294737ee577713e888a4243bb281d8bd4ab7c5
--- /dev/null
+++ b/cluster/pubsub.go
@@ -0,0 +1,59 @@
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/extensions"
+)
+
+const PubSubDeliveryName = "$pubsub-delivery"
+
+var pubsubExtensionID = extensions.NextExtensionID()
+
+type PubSub struct {
+ cluster *Cluster
+}
+
+func NewPubSub(cluster *Cluster) *PubSub {
+ p := &PubSub{
+ cluster: cluster,
+ }
+ cluster.ActorSystem.Extensions.Register(p)
+ return p
+}
+
+// Start the PubSubMemberDeliveryActor
+func (p *PubSub) Start() {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return NewPubSubMemberDeliveryActor(p.cluster.Config.PubSubConfig.SubscriberTimeout)
+ })
+ _, err := p.cluster.ActorSystem.Root.SpawnNamed(props, PubSubDeliveryName)
+ if err != nil {
+ panic(err) // let it crash
+ }
+ plog.Info("Started Cluster PubSub")
+}
+
+func (p *PubSub) ExtensionID() extensions.ExtensionID {
+ return pubsubExtensionID
+}
+
+type PubSubConfig struct {
+ // SubscriberTimeout is a timeout used when delivering a message batch to a subscriber. Default is 5s.
+ //
+ // This value gets rounded to seconds for optimization of cancellation token creation. Note that internally,
+ // cluster request is used to deliver messages to ClusterIdentity subscribers.
+ SubscriberTimeout time.Duration
+}
+
+func newPubSubConfig() *PubSubConfig {
+ return &PubSubConfig{
+ SubscriberTimeout: 5 * time.Second,
+ }
+}
+
+// GetPubSub returns the PubSub extension from the actor system
+func GetPubSub(system *actor.ActorSystem) *PubSub {
+ return system.Extensions.Get(pubsubExtensionID).(*PubSub)
+}
diff --git a/cluster/pubsub.pb.go b/cluster/pubsub.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..b19a78c4a759f26d9b1c649dfe2e4cec840ed24f
--- /dev/null
+++ b/cluster/pubsub.pb.go
@@ -0,0 +1,1346 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: pubsub.proto
+
+package cluster
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ durationpb "google.golang.org/protobuf/types/known/durationpb"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// Delivery status as seen by the delivery actor
+type DeliveryStatus int32
+
+const (
+ // Message was put in the queue of the subscriber
+ DeliveryStatus_Delivered DeliveryStatus = 0
+ // Message did not reach subscriber, because it was dead
+ DeliveryStatus_SubscriberNoLongerReachable DeliveryStatus = 1
+ // Delivery timed out
+ DeliveryStatus_Timeout DeliveryStatus = 2
+ // Some other problem happened
+ DeliveryStatus_OtherError DeliveryStatus = 127
+)
+
+// Enum value maps for DeliveryStatus.
+var (
+ DeliveryStatus_name = map[int32]string{
+ 0: "Delivered",
+ 1: "SubscriberNoLongerReachable",
+ 2: "Timeout",
+ 127: "OtherError",
+ }
+ DeliveryStatus_value = map[string]int32{
+ "Delivered": 0,
+ "SubscriberNoLongerReachable": 1,
+ "Timeout": 2,
+ "OtherError": 127,
+ }
+)
+
+func (x DeliveryStatus) Enum() *DeliveryStatus {
+ p := new(DeliveryStatus)
+ *p = x
+ return p
+}
+
+func (x DeliveryStatus) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (DeliveryStatus) Descriptor() protoreflect.EnumDescriptor {
+ return file_pubsub_proto_enumTypes[0].Descriptor()
+}
+
+func (DeliveryStatus) Type() protoreflect.EnumType {
+ return &file_pubsub_proto_enumTypes[0]
+}
+
+func (x DeliveryStatus) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use DeliveryStatus.Descriptor instead.
+func (DeliveryStatus) EnumDescriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{0}
+}
+
+// Status of the whole published batch or single message
+type PublishStatus int32
+
+const (
+ // Batch or message was successfully published according to the delivery guarantees
+ PublishStatus_Ok PublishStatus = 0
+ // Topic failed to forward the message
+ PublishStatus_Failed PublishStatus = 1
+)
+
+// Enum value maps for PublishStatus.
+var (
+ PublishStatus_name = map[int32]string{
+ 0: "Ok",
+ 1: "Failed",
+ }
+ PublishStatus_value = map[string]int32{
+ "Ok": 0,
+ "Failed": 1,
+ }
+)
+
+func (x PublishStatus) Enum() *PublishStatus {
+ p := new(PublishStatus)
+ *p = x
+ return p
+}
+
+func (x PublishStatus) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (PublishStatus) Descriptor() protoreflect.EnumDescriptor {
+ return file_pubsub_proto_enumTypes[1].Descriptor()
+}
+
+func (PublishStatus) Type() protoreflect.EnumType {
+ return &file_pubsub_proto_enumTypes[1]
+}
+
+func (x PublishStatus) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use PublishStatus.Descriptor instead.
+func (PublishStatus) EnumDescriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{1}
+}
+
+// Identifies a subscriber by either ClusterIdentity or PID
+type SubscriberIdentity struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Types that are assignable to Identity:
+ //
+ // *SubscriberIdentity_Pid
+ // *SubscriberIdentity_ClusterIdentity
+ Identity isSubscriberIdentity_Identity `protobuf_oneof:"Identity"`
+}
+
+func (x *SubscriberIdentity) Reset() {
+ *x = SubscriberIdentity{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SubscriberIdentity) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SubscriberIdentity) ProtoMessage() {}
+
+func (x *SubscriberIdentity) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SubscriberIdentity.ProtoReflect.Descriptor instead.
+func (*SubscriberIdentity) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{0}
+}
+
+func (m *SubscriberIdentity) GetIdentity() isSubscriberIdentity_Identity {
+ if m != nil {
+ return m.Identity
+ }
+ return nil
+}
+
+func (x *SubscriberIdentity) GetPid() *actor.PID {
+ if x, ok := x.GetIdentity().(*SubscriberIdentity_Pid); ok {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *SubscriberIdentity) GetClusterIdentity() *ClusterIdentity {
+ if x, ok := x.GetIdentity().(*SubscriberIdentity_ClusterIdentity); ok {
+ return x.ClusterIdentity
+ }
+ return nil
+}
+
+type isSubscriberIdentity_Identity interface {
+ isSubscriberIdentity_Identity()
+}
+
+type SubscriberIdentity_Pid struct {
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3,oneof"`
+}
+
+type SubscriberIdentity_ClusterIdentity struct {
+ ClusterIdentity *ClusterIdentity `protobuf:"bytes,2,opt,name=cluster_identity,json=clusterIdentity,proto3,oneof"`
+}
+
+func (*SubscriberIdentity_Pid) isSubscriberIdentity_Identity() {}
+
+func (*SubscriberIdentity_ClusterIdentity) isSubscriberIdentity_Identity() {}
+
+// First request to initialize the actor.
+type Initialize struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ IdleTimeout *durationpb.Duration `protobuf:"bytes,1,opt,name=idleTimeout,proto3" json:"idleTimeout,omitempty"`
+}
+
+func (x *Initialize) Reset() {
+ *x = Initialize{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Initialize) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Initialize) ProtoMessage() {}
+
+func (x *Initialize) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Initialize.ProtoReflect.Descriptor instead.
+func (*Initialize) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Initialize) GetIdleTimeout() *durationpb.Duration {
+ if x != nil {
+ return x.IdleTimeout
+ }
+ return nil
+}
+
+type Acknowledge struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *Acknowledge) Reset() {
+ *x = Acknowledge{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Acknowledge) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Acknowledge) ProtoMessage() {}
+
+func (x *Acknowledge) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Acknowledge.ProtoReflect.Descriptor instead.
+func (*Acknowledge) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{2}
+}
+
+// A list of subscribers
+type Subscribers struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Subscribers []*SubscriberIdentity `protobuf:"bytes,1,rep,name=subscribers,proto3" json:"subscribers,omitempty"`
+}
+
+func (x *Subscribers) Reset() {
+ *x = Subscribers{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Subscribers) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Subscribers) ProtoMessage() {}
+
+func (x *Subscribers) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Subscribers.ProtoReflect.Descriptor instead.
+func (*Subscribers) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *Subscribers) GetSubscribers() []*SubscriberIdentity {
+ if x != nil {
+ return x.Subscribers
+ }
+ return nil
+}
+
+// Sent to topic actor to add a subscriber
+type SubscribeRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Subscriber *SubscriberIdentity `protobuf:"bytes,1,opt,name=subscriber,proto3" json:"subscriber,omitempty"`
+}
+
+func (x *SubscribeRequest) Reset() {
+ *x = SubscribeRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SubscribeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SubscribeRequest) ProtoMessage() {}
+
+func (x *SubscribeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SubscribeRequest.ProtoReflect.Descriptor instead.
+func (*SubscribeRequest) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *SubscribeRequest) GetSubscriber() *SubscriberIdentity {
+ if x != nil {
+ return x.Subscriber
+ }
+ return nil
+}
+
+// Subscribe acknowledgement
+type SubscribeResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *SubscribeResponse) Reset() {
+ *x = SubscribeResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SubscribeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SubscribeResponse) ProtoMessage() {}
+
+func (x *SubscribeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SubscribeResponse.ProtoReflect.Descriptor instead.
+func (*SubscribeResponse) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{5}
+}
+
+// Sent to topic actor to remove a subscriber
+type UnsubscribeRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Subscriber *SubscriberIdentity `protobuf:"bytes,1,opt,name=subscriber,proto3" json:"subscriber,omitempty"`
+}
+
+func (x *UnsubscribeRequest) Reset() {
+ *x = UnsubscribeRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *UnsubscribeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnsubscribeRequest) ProtoMessage() {}
+
+func (x *UnsubscribeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnsubscribeRequest.ProtoReflect.Descriptor instead.
+func (*UnsubscribeRequest) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *UnsubscribeRequest) GetSubscriber() *SubscriberIdentity {
+ if x != nil {
+ return x.Subscriber
+ }
+ return nil
+}
+
+// Unsubscribe acknowledgement
+type UnsubscribeResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *UnsubscribeResponse) Reset() {
+ *x = UnsubscribeResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *UnsubscribeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnsubscribeResponse) ProtoMessage() {}
+
+func (x *UnsubscribeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnsubscribeResponse.ProtoReflect.Descriptor instead.
+func (*UnsubscribeResponse) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{7}
+}
+
+// Message sent from publisher to topic actor
+// See also PubSubBatch
+type PubSubBatchTransport struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TypeNames []string `protobuf:"bytes,1,rep,name=type_names,json=typeNames,proto3" json:"type_names,omitempty"`
+ Envelopes []*PubSubEnvelope `protobuf:"bytes,2,rep,name=envelopes,proto3" json:"envelopes,omitempty"`
+}
+
+func (x *PubSubBatchTransport) Reset() {
+ *x = PubSubBatchTransport{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PubSubBatchTransport) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PubSubBatchTransport) ProtoMessage() {}
+
+func (x *PubSubBatchTransport) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PubSubBatchTransport.ProtoReflect.Descriptor instead.
+func (*PubSubBatchTransport) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *PubSubBatchTransport) GetTypeNames() []string {
+ if x != nil {
+ return x.TypeNames
+ }
+ return nil
+}
+
+func (x *PubSubBatchTransport) GetEnvelopes() []*PubSubEnvelope {
+ if x != nil {
+ return x.Envelopes
+ }
+ return nil
+}
+
+// Contains message byte representation and type reference
+type PubSubEnvelope struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TypeId int32 `protobuf:"varint,1,opt,name=type_id,json=typeId,proto3" json:"type_id,omitempty"`
+ MessageData []byte `protobuf:"bytes,2,opt,name=message_data,json=messageData,proto3" json:"message_data,omitempty"`
+ SerializerId int32 `protobuf:"varint,3,opt,name=serializer_id,json=serializerId,proto3" json:"serializer_id,omitempty"`
+}
+
+func (x *PubSubEnvelope) Reset() {
+ *x = PubSubEnvelope{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PubSubEnvelope) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PubSubEnvelope) ProtoMessage() {}
+
+func (x *PubSubEnvelope) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[9]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PubSubEnvelope.ProtoReflect.Descriptor instead.
+func (*PubSubEnvelope) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *PubSubEnvelope) GetTypeId() int32 {
+ if x != nil {
+ return x.TypeId
+ }
+ return 0
+}
+
+func (x *PubSubEnvelope) GetMessageData() []byte {
+ if x != nil {
+ return x.MessageData
+ }
+ return nil
+}
+
+func (x *PubSubEnvelope) GetSerializerId() int32 {
+ if x != nil {
+ return x.SerializerId
+ }
+ return 0
+}
+
+// Message sent from topic to delivery actor
+type DeliverBatchRequestTransport struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Subscribers *Subscribers `protobuf:"bytes,1,opt,name=subscribers,proto3" json:"subscribers,omitempty"`
+ Batch *PubSubBatchTransport `protobuf:"bytes,2,opt,name=batch,proto3" json:"batch,omitempty"`
+ Topic string `protobuf:"bytes,3,opt,name=topic,proto3" json:"topic,omitempty"`
+}
+
+func (x *DeliverBatchRequestTransport) Reset() {
+ *x = DeliverBatchRequestTransport{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DeliverBatchRequestTransport) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeliverBatchRequestTransport) ProtoMessage() {}
+
+func (x *DeliverBatchRequestTransport) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[10]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeliverBatchRequestTransport.ProtoReflect.Descriptor instead.
+func (*DeliverBatchRequestTransport) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *DeliverBatchRequestTransport) GetSubscribers() *Subscribers {
+ if x != nil {
+ return x.Subscribers
+ }
+ return nil
+}
+
+func (x *DeliverBatchRequestTransport) GetBatch() *PubSubBatchTransport {
+ if x != nil {
+ return x.Batch
+ }
+ return nil
+}
+
+func (x *DeliverBatchRequestTransport) GetTopic() string {
+ if x != nil {
+ return x.Topic
+ }
+ return ""
+}
+
+// Message sent from delivery actor to topic to notify of subscribers that fail to process the messages
+type NotifyAboutFailingSubscribersRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ InvalidDeliveries []*SubscriberDeliveryReport `protobuf:"bytes,1,rep,name=invalid_deliveries,json=invalidDeliveries,proto3" json:"invalid_deliveries,omitempty"`
+}
+
+func (x *NotifyAboutFailingSubscribersRequest) Reset() {
+ *x = NotifyAboutFailingSubscribersRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NotifyAboutFailingSubscribersRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NotifyAboutFailingSubscribersRequest) ProtoMessage() {}
+
+func (x *NotifyAboutFailingSubscribersRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[11]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NotifyAboutFailingSubscribersRequest.ProtoReflect.Descriptor instead.
+func (*NotifyAboutFailingSubscribersRequest) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *NotifyAboutFailingSubscribersRequest) GetInvalidDeliveries() []*SubscriberDeliveryReport {
+ if x != nil {
+ return x.InvalidDeliveries
+ }
+ return nil
+}
+
+// Ack to the delivery actor after notification of subscribers that fail to process the messages
+type NotifyAboutFailingSubscribersResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *NotifyAboutFailingSubscribersResponse) Reset() {
+ *x = NotifyAboutFailingSubscribersResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *NotifyAboutFailingSubscribersResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NotifyAboutFailingSubscribersResponse) ProtoMessage() {}
+
+func (x *NotifyAboutFailingSubscribersResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[12]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use NotifyAboutFailingSubscribersResponse.ProtoReflect.Descriptor instead.
+func (*NotifyAboutFailingSubscribersResponse) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{12}
+}
+
+// Contains information about a failed delivery
+type SubscriberDeliveryReport struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Subscriber *SubscriberIdentity `protobuf:"bytes,1,opt,name=subscriber,proto3" json:"subscriber,omitempty"`
+ Status DeliveryStatus `protobuf:"varint,2,opt,name=status,proto3,enum=cluster.DeliveryStatus" json:"status,omitempty"`
+}
+
+func (x *SubscriberDeliveryReport) Reset() {
+ *x = SubscriberDeliveryReport{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *SubscriberDeliveryReport) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SubscriberDeliveryReport) ProtoMessage() {}
+
+func (x *SubscriberDeliveryReport) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[13]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SubscriberDeliveryReport.ProtoReflect.Descriptor instead.
+func (*SubscriberDeliveryReport) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *SubscriberDeliveryReport) GetSubscriber() *SubscriberIdentity {
+ if x != nil {
+ return x.Subscriber
+ }
+ return nil
+}
+
+func (x *SubscriberDeliveryReport) GetStatus() DeliveryStatus {
+ if x != nil {
+ return x.Status
+ }
+ return DeliveryStatus_Delivered
+}
+
+// Message posted to subscriber's mailbox, that is then unrolled to single messages, and has ability to auto respond
+// See also PubSubAutoRespondBatch
+type PubSubAutoRespondBatchTransport struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TypeNames []string `protobuf:"bytes,1,rep,name=type_names,json=typeNames,proto3" json:"type_names,omitempty"`
+ Envelopes []*PubSubEnvelope `protobuf:"bytes,2,rep,name=envelopes,proto3" json:"envelopes,omitempty"`
+}
+
+func (x *PubSubAutoRespondBatchTransport) Reset() {
+ *x = PubSubAutoRespondBatchTransport{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PubSubAutoRespondBatchTransport) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PubSubAutoRespondBatchTransport) ProtoMessage() {}
+
+func (x *PubSubAutoRespondBatchTransport) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[14]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PubSubAutoRespondBatchTransport.ProtoReflect.Descriptor instead.
+func (*PubSubAutoRespondBatchTransport) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *PubSubAutoRespondBatchTransport) GetTypeNames() []string {
+ if x != nil {
+ return x.TypeNames
+ }
+ return nil
+}
+
+func (x *PubSubAutoRespondBatchTransport) GetEnvelopes() []*PubSubEnvelope {
+ if x != nil {
+ return x.Envelopes
+ }
+ return nil
+}
+
+// Publish ack/nack response
+type PublishResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Status of the whole published batch or single message
+ Status PublishStatus `protobuf:"varint,1,opt,name=status,proto3,enum=cluster.PublishStatus" json:"status,omitempty"`
+}
+
+func (x *PublishResponse) Reset() {
+ *x = PublishResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_proto_msgTypes[15]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *PublishResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PublishResponse) ProtoMessage() {}
+
+func (x *PublishResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_proto_msgTypes[15]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use PublishResponse.ProtoReflect.Descriptor instead.
+func (*PublishResponse) Descriptor() ([]byte, []int) {
+ return file_pubsub_proto_rawDescGZIP(), []int{15}
+}
+
+func (x *PublishResponse) GetStatus() PublishStatus {
+ if x != nil {
+ return x.Status
+ }
+ return PublishStatus_Ok
+}
+
+var File_pubsub_proto protoreflect.FileDescriptor
+
+var file_pubsub_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x1a, 0x0d, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x01, 0x0a, 0x12, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
+ 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1e, 0x0a, 0x03, 0x70, 0x69,
+ 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e,
+ 0x50, 0x49, 0x44, 0x48, 0x00, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x45, 0x0a, 0x10, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x43,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x00,
+ 0x52, 0x0f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74,
+ 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x49, 0x0a,
+ 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x69,
+ 0x64, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+ 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+ 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x69, 0x64, 0x6c,
+ 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x0d, 0x0a, 0x0b, 0x41, 0x63, 0x6b, 0x6e,
+ 0x6f, 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x22, 0x4c, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x73, 0x63,
+ 0x72, 0x69, 0x62, 0x65, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
+ 0x69, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6c,
+ 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72,
+ 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72,
+ 0x69, 0x62, 0x65, 0x72, 0x73, 0x22, 0x4f, 0x0a, 0x10, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+ 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x73, 0x75, 0x62,
+ 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62,
+ 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73,
+ 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x22, 0x13, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
+ 0x69, 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x12, 0x55,
+ 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x18,
+ 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e,
+ 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
+ 0x74, 0x79, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x22, 0x15,
+ 0x0a, 0x13, 0x55, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x73,
+ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6c, 0x0a, 0x14, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x42,
+ 0x61, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1d, 0x0a,
+ 0x0a, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+ 0x09, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x09,
+ 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
+ 0x17, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62,
+ 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x09, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f,
+ 0x70, 0x65, 0x73, 0x22, 0x71, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x45, 0x6e, 0x76,
+ 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x21,
+ 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74,
+ 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x5f,
+ 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
+ 0x69, 0x7a, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa1, 0x01, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x69, 0x76,
+ 0x65, 0x72, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x72,
+ 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x36, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x73, 0x63,
+ 0x72, 0x69, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,
+ 0x72, 0x73, 0x52, 0x0b, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x73, 0x12,
+ 0x33, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,
+ 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x42,
+ 0x61, 0x74, 0x63, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x62,
+ 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x18, 0x03, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x22, 0x78, 0x0a, 0x24, 0x4e, 0x6f,
+ 0x74, 0x69, 0x66, 0x79, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67,
+ 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x12, 0x50, 0x0a, 0x12, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x64, 0x65,
+ 0x6c, 0x69, 0x76, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21,
+ 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+ 0x62, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72,
+ 0x74, 0x52, 0x11, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65,
+ 0x72, 0x69, 0x65, 0x73, 0x22, 0x27, 0x0a, 0x25, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x41, 0x62,
+ 0x6f, 0x75, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
+ 0x69, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x88, 0x01,
+ 0x0a, 0x18, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x69,
+ 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x73, 0x75,
+ 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
+ 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+ 0x62, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x73, 0x75, 0x62,
+ 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
+ 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
+ 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
+ 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x77, 0x0a, 0x1f, 0x50, 0x75, 0x62, 0x53,
+ 0x75, 0x62, 0x41, 0x75, 0x74, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x42, 0x61, 0x74,
+ 0x63, 0x68, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x74,
+ 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,
+ 0x09, 0x74, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x09, 0x65, 0x6e,
+ 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e,
+ 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50, 0x75, 0x62, 0x53, 0x75, 0x62, 0x45, 0x6e,
+ 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x09, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65,
+ 0x73, 0x22, 0x41, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70,
+ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x2e, 0x50,
+ 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74,
+ 0x61, 0x74, 0x75, 0x73, 0x2a, 0x5d, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x79,
+ 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x69, 0x76, 0x65,
+ 0x72, 0x65, 0x64, 0x10, 0x00, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+ 0x62, 0x65, 0x72, 0x4e, 0x6f, 0x4c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x52, 0x65, 0x61, 0x63, 0x68,
+ 0x61, 0x62, 0x6c, 0x65, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75,
+ 0x74, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x45, 0x72, 0x72, 0x6f,
+ 0x72, 0x10, 0x7f, 0x2a, 0x23, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x74,
+ 0x61, 0x74, 0x75, 0x73, 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x6b, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
+ 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e,
+ 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63,
+ 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_pubsub_proto_rawDescOnce sync.Once
+ file_pubsub_proto_rawDescData = file_pubsub_proto_rawDesc
+)
+
+func file_pubsub_proto_rawDescGZIP() []byte {
+ file_pubsub_proto_rawDescOnce.Do(func() {
+ file_pubsub_proto_rawDescData = protoimpl.X.CompressGZIP(file_pubsub_proto_rawDescData)
+ })
+ return file_pubsub_proto_rawDescData
+}
+
+var file_pubsub_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
+var file_pubsub_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
+var file_pubsub_proto_goTypes = []interface{}{
+ (DeliveryStatus)(0), // 0: cluster.DeliveryStatus
+ (PublishStatus)(0), // 1: cluster.PublishStatus
+ (*SubscriberIdentity)(nil), // 2: cluster.SubscriberIdentity
+ (*Initialize)(nil), // 3: cluster.Initialize
+ (*Acknowledge)(nil), // 4: cluster.Acknowledge
+ (*Subscribers)(nil), // 5: cluster.Subscribers
+ (*SubscribeRequest)(nil), // 6: cluster.SubscribeRequest
+ (*SubscribeResponse)(nil), // 7: cluster.SubscribeResponse
+ (*UnsubscribeRequest)(nil), // 8: cluster.UnsubscribeRequest
+ (*UnsubscribeResponse)(nil), // 9: cluster.UnsubscribeResponse
+ (*PubSubBatchTransport)(nil), // 10: cluster.PubSubBatchTransport
+ (*PubSubEnvelope)(nil), // 11: cluster.PubSubEnvelope
+ (*DeliverBatchRequestTransport)(nil), // 12: cluster.DeliverBatchRequestTransport
+ (*NotifyAboutFailingSubscribersRequest)(nil), // 13: cluster.NotifyAboutFailingSubscribersRequest
+ (*NotifyAboutFailingSubscribersResponse)(nil), // 14: cluster.NotifyAboutFailingSubscribersResponse
+ (*SubscriberDeliveryReport)(nil), // 15: cluster.SubscriberDeliveryReport
+ (*PubSubAutoRespondBatchTransport)(nil), // 16: cluster.PubSubAutoRespondBatchTransport
+ (*PublishResponse)(nil), // 17: cluster.PublishResponse
+ (*actor.PID)(nil), // 18: actor.PID
+ (*ClusterIdentity)(nil), // 19: cluster.ClusterIdentity
+ (*durationpb.Duration)(nil), // 20: google.protobuf.Duration
+}
+var file_pubsub_proto_depIdxs = []int32{
+ 18, // 0: cluster.SubscriberIdentity.pid:type_name -> actor.PID
+ 19, // 1: cluster.SubscriberIdentity.cluster_identity:type_name -> cluster.ClusterIdentity
+ 20, // 2: cluster.Initialize.idleTimeout:type_name -> google.protobuf.Duration
+ 2, // 3: cluster.Subscribers.subscribers:type_name -> cluster.SubscriberIdentity
+ 2, // 4: cluster.SubscribeRequest.subscriber:type_name -> cluster.SubscriberIdentity
+ 2, // 5: cluster.UnsubscribeRequest.subscriber:type_name -> cluster.SubscriberIdentity
+ 11, // 6: cluster.PubSubBatchTransport.envelopes:type_name -> cluster.PubSubEnvelope
+ 5, // 7: cluster.DeliverBatchRequestTransport.subscribers:type_name -> cluster.Subscribers
+ 10, // 8: cluster.DeliverBatchRequestTransport.batch:type_name -> cluster.PubSubBatchTransport
+ 15, // 9: cluster.NotifyAboutFailingSubscribersRequest.invalid_deliveries:type_name -> cluster.SubscriberDeliveryReport
+ 2, // 10: cluster.SubscriberDeliveryReport.subscriber:type_name -> cluster.SubscriberIdentity
+ 0, // 11: cluster.SubscriberDeliveryReport.status:type_name -> cluster.DeliveryStatus
+ 11, // 12: cluster.PubSubAutoRespondBatchTransport.envelopes:type_name -> cluster.PubSubEnvelope
+ 1, // 13: cluster.PublishResponse.status:type_name -> cluster.PublishStatus
+ 14, // [14:14] is the sub-list for method output_type
+ 14, // [14:14] is the sub-list for method input_type
+ 14, // [14:14] is the sub-list for extension type_name
+ 14, // [14:14] is the sub-list for extension extendee
+ 0, // [0:14] is the sub-list for field type_name
+}
+
+func init() { file_pubsub_proto_init() }
+func file_pubsub_proto_init() {
+ if File_pubsub_proto != nil {
+ return
+ }
+ file_cluster_proto_init()
+ if !protoimpl.UnsafeEnabled {
+ file_pubsub_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SubscriberIdentity); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Initialize); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Acknowledge); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Subscribers); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SubscribeRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SubscribeResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*UnsubscribeRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*UnsubscribeResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PubSubBatchTransport); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PubSubEnvelope); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DeliverBatchRequestTransport); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NotifyAboutFailingSubscribersRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*NotifyAboutFailingSubscribersResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*SubscriberDeliveryReport); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PubSubAutoRespondBatchTransport); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_pubsub_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*PublishResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ file_pubsub_proto_msgTypes[0].OneofWrappers = []interface{}{
+ (*SubscriberIdentity_Pid)(nil),
+ (*SubscriberIdentity_ClusterIdentity)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_pubsub_proto_rawDesc,
+ NumEnums: 2,
+ NumMessages: 16,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_pubsub_proto_goTypes,
+ DependencyIndexes: file_pubsub_proto_depIdxs,
+ EnumInfos: file_pubsub_proto_enumTypes,
+ MessageInfos: file_pubsub_proto_msgTypes,
+ }.Build()
+ File_pubsub_proto = out.File
+ file_pubsub_proto_rawDesc = nil
+ file_pubsub_proto_goTypes = nil
+ file_pubsub_proto_depIdxs = nil
+}
diff --git a/cluster/pubsub.proto b/cluster/pubsub.proto
new file mode 100644
index 0000000000000000000000000000000000000000..18314f16732323166c3d38911ef05b8fc0848662
--- /dev/null
+++ b/cluster/pubsub.proto
@@ -0,0 +1,115 @@
+syntax = "proto3";
+package cluster;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster";
+
+import "cluster.proto";
+import "google/protobuf/duration.proto";
+import "actor.proto";
+
+// Identifies a subscriber by either ClusterIdentity or PID
+message SubscriberIdentity {
+ oneof Identity {
+ actor.PID pid = 1;
+ cluster.ClusterIdentity cluster_identity = 2;
+ }
+}
+
+// First request to initialize the actor.
+message Initialize {
+ google.protobuf.Duration idleTimeout = 1;
+}
+
+message Acknowledge {}
+
+// A list of subscribers
+message Subscribers {
+ repeated SubscriberIdentity subscribers = 1;
+}
+
+// Sent to topic actor to add a subscriber
+message SubscribeRequest {
+ SubscriberIdentity subscriber = 1;
+}
+
+// Subscribe acknowledgement
+message SubscribeResponse {}
+
+// Sent to topic actor to remove a subscriber
+message UnsubscribeRequest {
+ SubscriberIdentity subscriber = 1;
+}
+
+// Unsubscribe acknowledgement
+message UnsubscribeResponse {}
+
+// Message sent from publisher to topic actor
+// See also PubSubBatch
+message PubSubBatchTransport {
+ repeated string type_names = 1;
+ repeated PubSubEnvelope envelopes = 2;
+}
+
+// Contains message byte representation and type reference
+message PubSubEnvelope {
+ int32 type_id = 1;
+ bytes message_data = 2;
+ int32 serializer_id = 3;
+}
+
+// Message sent from topic to delivery actor
+message DeliverBatchRequestTransport {
+ Subscribers subscribers = 1;
+ PubSubBatchTransport batch = 2;
+ string topic = 3;
+}
+
+// Message sent from delivery actor to topic to notify of subscribers that fail to process the messages
+message NotifyAboutFailingSubscribersRequest {
+ repeated SubscriberDeliveryReport invalid_deliveries = 1;
+}
+
+// Ack to the delivery actor after notification of subscribers that fail to process the messages
+message NotifyAboutFailingSubscribersResponse {}
+
+// Contains information about a failed delivery
+message SubscriberDeliveryReport {
+ SubscriberIdentity subscriber = 1;
+ DeliveryStatus status = 2;
+}
+
+// Delivery status as seen by the delivery actor
+enum DeliveryStatus {
+ // Message was put in the queue of the subscriber
+ Delivered = 0;
+
+ // Message did not reach subscriber, because it was dead
+ SubscriberNoLongerReachable = 1;
+
+ // Delivery timed out
+ Timeout = 2;
+
+ // Some other problem happened
+ OtherError = 127;
+}
+
+// Message posted to subscriber's mailbox, that is then unrolled to single messages, and has ability to auto respond
+// See also PubSubAutoRespondBatch
+message PubSubAutoRespondBatchTransport {
+ repeated string type_names = 1;
+ repeated PubSubEnvelope envelopes = 2;
+}
+
+// Status of the whole published batch or single message
+enum PublishStatus {
+ // Batch or message was successfully published according to the delivery guarantees
+ Ok = 0;
+
+ // Topic failed to forward the message
+ Failed = 1;
+}
+
+// Publish ack/nack response
+message PublishResponse {
+ // Status of the whole published batch or single message
+ PublishStatus status = 1;
+}
diff --git a/cluster/pubsub_batch.go b/cluster/pubsub_batch.go
new file mode 100644
index 0000000000000000000000000000000000000000..5bcad054fd07f62b5ed5a0272c71261061db4e1f
--- /dev/null
+++ b/cluster/pubsub_batch.go
@@ -0,0 +1,119 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+type PubSubBatch struct {
+ Envelopes []interface{}
+}
+
+// Serialize converts a PubSubBatch to a PubSubBatchTransport.
+func (b *PubSubBatch) Serialize() remote.RootSerialized {
+ batch := &PubSubBatchTransport{
+ TypeNames: make([]string, 0),
+ Envelopes: make([]*PubSubEnvelope, 0),
+ }
+
+ for _, envelope := range b.Envelopes {
+ var serializerId int32
+ messageData, typeName, err := remote.Serialize(envelope, serializerId)
+ if err != nil {
+ panic(err)
+ }
+ // batch.TypeNames.IndexOf(typeName)
+ typeIndex := -1
+ for i, t := range batch.TypeNames {
+ if t == typeName {
+ typeIndex = i
+ break
+ }
+ }
+ if typeIndex == -1 {
+ batch.TypeNames = append(batch.TypeNames, typeName)
+ typeIndex = len(batch.TypeNames) - 1
+ }
+ batch.Envelopes = append(batch.Envelopes, &PubSubEnvelope{
+ MessageData: messageData,
+ TypeId: int32(typeIndex),
+ SerializerId: serializerId,
+ })
+ }
+ return batch
+}
+
+// Deserialize converts a PubSubBatchTransport to a PubSubBatch.
+func (t *PubSubBatchTransport) Deserialize() remote.RootSerializable {
+ b := &PubSubBatch{
+ Envelopes: make([]interface{}, 0),
+ }
+
+ for _, envelope := range t.Envelopes {
+ message, err := remote.Deserialize(envelope.MessageData, t.TypeNames[envelope.TypeId], envelope.SerializerId)
+ if err != nil {
+ panic(err)
+ }
+ b.Envelopes = append(b.Envelopes, message)
+ }
+ return b
+}
+
+type DeliverBatchRequest struct {
+ Subscribers *Subscribers
+ PubSubBatch *PubSubBatch
+ Topic string
+}
+
+func (d *DeliverBatchRequest) Serialize() remote.RootSerialized {
+ return &DeliverBatchRequestTransport{
+ Subscribers: d.Subscribers,
+ Batch: d.PubSubBatch.Serialize().(*PubSubBatchTransport),
+ Topic: d.Topic,
+ }
+}
+
+func (t *DeliverBatchRequestTransport) Deserialize() remote.RootSerializable {
+ return &DeliverBatchRequest{
+ Subscribers: t.Subscribers,
+ PubSubBatch: t.Batch.Deserialize().(*PubSubBatch),
+ Topic: t.Topic,
+ }
+}
+
+type PubSubAutoRespondBatch struct {
+ Envelopes []interface{}
+}
+
+// Serialize converts a PubSubAutoRespondBatch to a PubSubAutoRespondBatchTransport.
+func (b *PubSubAutoRespondBatch) Serialize() remote.RootSerialized {
+ batch := &PubSubBatch{Envelopes: b.Envelopes}
+ transport := batch.Serialize().(*PubSubBatchTransport)
+ return &PubSubAutoRespondBatchTransport{
+ TypeNames: transport.TypeNames,
+ Envelopes: transport.Envelopes,
+ }
+}
+
+// GetAutoResponse returns a PublishResponse.
+func (b *PubSubAutoRespondBatch) GetAutoResponse(_ actor.Context) interface{} {
+ return &PublishResponse{
+ Status: PublishStatus_Ok,
+ }
+}
+
+// GetMessages returns the message.
+func (b *PubSubAutoRespondBatch) GetMessages() []interface{} {
+ return b.Envelopes
+}
+
+// Deserialize converts a PubSubAutoRespondBatchTransport to a PubSubAutoRespondBatch.
+func (t *PubSubAutoRespondBatchTransport) Deserialize() remote.RootSerializable {
+ batch := &PubSubBatchTransport{
+ TypeNames: t.TypeNames,
+ Envelopes: t.Envelopes,
+ }
+ return &PubSubAutoRespondBatch{
+ Envelopes: batch.Deserialize().(*PubSubBatch).Envelopes,
+ }
+}
diff --git a/cluster/pubsub_delivery.go b/cluster/pubsub_delivery.go
new file mode 100644
index 0000000000000000000000000000000000000000..34125087840c7dcd6b3a78e20a8eed490c98d142
--- /dev/null
+++ b/cluster/pubsub_delivery.go
@@ -0,0 +1,106 @@
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "gitee.com/simplexyz/simpleactor-go/remote"
+)
+
+var pubsubMemberDeliveryLogThrottle = actor.NewThrottle(10, time.Second, func(i int32) {
+ plog.Warn("[PubSubMemberDeliveryActor] Throttled logs", log.Int("count", int(i)))
+})
+
+type PubSubMemberDeliveryActor struct {
+ subscriberTimeout time.Duration
+}
+
+func NewPubSubMemberDeliveryActor(subscriberTimeout time.Duration) *PubSubMemberDeliveryActor {
+ return &PubSubMemberDeliveryActor{
+ subscriberTimeout: subscriberTimeout,
+ }
+}
+
+func (p *PubSubMemberDeliveryActor) Receive(c actor.Context) {
+ if batch, ok := c.Message().(*DeliverBatchRequest); ok {
+ topicBatch := &PubSubAutoRespondBatch{Envelopes: batch.PubSubBatch.Envelopes}
+ siList := batch.Subscribers.Subscribers
+
+ invalidDeliveries := make([]*SubscriberDeliveryReport, 0, len(siList))
+
+ type futureWithIdentity struct {
+ future *actor.Future
+ identity *SubscriberIdentity
+ }
+ futureList := make([]futureWithIdentity, 0, len(siList))
+ for _, identity := range siList {
+ f := p.DeliverBatch(c, topicBatch, identity)
+ if f != nil {
+ futureList = append(futureList, futureWithIdentity{future: f, identity: identity})
+ }
+ }
+
+ for _, fWithIdentity := range futureList {
+ _, err := fWithIdentity.future.Result()
+ identityLog := func(err error) {
+ if pubsubMemberDeliveryLogThrottle() == actor.Open {
+ if fWithIdentity.identity.GetPid() != nil {
+ plog.Info("Pub-sub message delivered to PID", log.String("pid", fWithIdentity.identity.GetPid().String()))
+ } else if fWithIdentity.identity.GetClusterIdentity() != nil {
+ plog.Info("Pub-sub message delivered to cluster identity", log.String("cluster identity", fWithIdentity.identity.GetClusterIdentity().String()))
+ }
+ }
+ }
+
+ status := DeliveryStatus_Delivered
+ if err != nil {
+ switch err {
+ case actor.ErrTimeout, remote.ErrTimeout:
+ identityLog(err)
+ status = DeliveryStatus_Timeout
+ case actor.ErrDeadLetter, remote.ErrDeadLetter:
+ identityLog(err)
+ status = DeliveryStatus_SubscriberNoLongerReachable
+ default:
+ identityLog(err)
+ status = DeliveryStatus_OtherError
+ }
+ }
+ if status != DeliveryStatus_Delivered {
+ invalidDeliveries = append(invalidDeliveries, &SubscriberDeliveryReport{Status: status, Subscriber: fWithIdentity.identity})
+ }
+ }
+
+ if len(invalidDeliveries) > 0 {
+ cluster := GetCluster(c.ActorSystem())
+ // we use cluster.Call to locate the topic actor in the cluster
+ _, _ = cluster.Call(batch.Topic, TopicActorKind, &NotifyAboutFailingSubscribersRequest{InvalidDeliveries: invalidDeliveries})
+ }
+ }
+}
+
+// DeliverBatch delivers PubSubAutoRespondBatch to SubscriberIdentity.
+func (p *PubSubMemberDeliveryActor) DeliverBatch(c actor.Context, batch *PubSubAutoRespondBatch, s *SubscriberIdentity) *actor.Future {
+ if pid := s.GetPid(); pid != nil {
+ return p.DeliverToPid(c, batch, pid)
+ }
+ if ci := s.GetClusterIdentity(); ci != nil {
+ return p.DeliverToClusterIdentity(c, batch, ci)
+ }
+ return nil
+}
+
+// DeliverToPid delivers PubSubAutoRespondBatch to PID.
+func (p *PubSubMemberDeliveryActor) DeliverToPid(c actor.Context, batch *PubSubAutoRespondBatch, pid *actor.PID) *actor.Future {
+ return c.RequestFuture(pid, batch, p.subscriberTimeout)
+}
+
+// DeliverToClusterIdentity delivers PubSubAutoRespondBatch to ClusterIdentity.
+func (p *PubSubMemberDeliveryActor) DeliverToClusterIdentity(c actor.Context, batch *PubSubAutoRespondBatch, ci *ClusterIdentity) *actor.Future {
+ cluster := GetCluster(c.ActorSystem())
+ // deliver to virtual actor
+ // delivery should always be possible, since a virtual actor always exists
+ pid := cluster.Get(ci.Identity, ci.Kind)
+ return c.RequestFuture(pid, batch, p.subscriberTimeout)
+}
diff --git a/cluster/pubsub_extensions.go b/cluster/pubsub_extensions.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c752b85879d3e4625c7e57a56a085145bf81030
--- /dev/null
+++ b/cluster/pubsub_extensions.go
@@ -0,0 +1,77 @@
+package cluster
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// Publisher creates a new PubSub publisher that publishes messages directly to the TopicActor
+func (c *Cluster) Publisher() Publisher {
+ return NewPublisher(c)
+}
+
+// BatchingProducer create a new PubSub batching producer for specified topic, that publishes directly to the topic actor
+func (c *Cluster) BatchingProducer(topic string, opts ...BatchingProducerConfigOption) *BatchingProducer {
+ return NewBatchingProducer(c.Publisher(), topic, opts...)
+}
+
+// SubscribeByPid subscribes to a PubSub topic by subscriber PID
+func (c *Cluster) SubscribeByPid(topic string, pid *actor.PID, opts ...GrainCallOption) (*SubscribeResponse, error) {
+ res, err := c.Call(topic, TopicActorKind, &SubscribeRequest{
+ Subscriber: &SubscriberIdentity{Identity: &SubscriberIdentity_Pid{Pid: pid}},
+ }, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*SubscribeResponse), err
+}
+
+// SubscribeByClusterIdentity subscribes to a PubSub topic by cluster identity
+func (c *Cluster) SubscribeByClusterIdentity(topic string, identity *ClusterIdentity, opts ...GrainCallOption) (*SubscribeResponse, error) {
+ res, err := c.Call(topic, TopicActorKind, &SubscribeRequest{
+ Subscriber: &SubscriberIdentity{Identity: &SubscriberIdentity_ClusterIdentity{ClusterIdentity: identity}},
+ }, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*SubscribeResponse), err
+}
+
+// SubscribeWithReceive subscribe to a PubSub topic by providing a Receive function, that will be used to spawn a subscriber actor
+func (c *Cluster) SubscribeWithReceive(topic string, receive actor.ReceiveFunc, opts ...GrainCallOption) (*SubscribeResponse, error) {
+ props := actor.PropsFromFunc(receive)
+ pid := c.ActorSystem.Root.Spawn(props)
+ return c.SubscribeByPid(topic, pid, opts...)
+}
+
+// UnsubscribeByPid unsubscribes from a PubSub topic by subscriber PID
+func (c *Cluster) UnsubscribeByPid(topic string, pid *actor.PID, opts ...GrainCallOption) (*UnsubscribeResponse, error) {
+ res, err := c.Call(topic, TopicActorKind, &UnsubscribeRequest{
+ Subscriber: &SubscriberIdentity{Identity: &SubscriberIdentity_Pid{Pid: pid}},
+ }, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*UnsubscribeResponse), err
+}
+
+// UnsubscribeByClusterIdentity unsubscribes from a PubSub topic by cluster identity
+func (c *Cluster) UnsubscribeByClusterIdentity(topic string, identity *ClusterIdentity, opts ...GrainCallOption) (*UnsubscribeResponse, error) {
+ res, err := c.Call(topic, TopicActorKind, &UnsubscribeRequest{
+ Subscriber: &SubscriberIdentity{Identity: &SubscriberIdentity_ClusterIdentity{ClusterIdentity: identity}},
+ }, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*UnsubscribeResponse), err
+}
+
+// UnsubscribeByIdentityAndKind unsubscribes from a PubSub topic by cluster identity
+func (c *Cluster) UnsubscribeByIdentityAndKind(topic string, identity string, kind string, opts ...GrainCallOption) (*UnsubscribeResponse, error) {
+ res, err := c.Call(topic, TopicActorKind, &UnsubscribeRequest{
+ Subscriber: &SubscriberIdentity{Identity: &SubscriberIdentity_ClusterIdentity{ClusterIdentity: NewClusterIdentity(identity, kind)}},
+ }, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*UnsubscribeResponse), err
+}
diff --git a/cluster/pubsub_producer.go b/cluster/pubsub_producer.go
new file mode 100644
index 0000000000000000000000000000000000000000..cef9b0567cb2db1de6c25507d2bfd57e7d4dc0d1
--- /dev/null
+++ b/cluster/pubsub_producer.go
@@ -0,0 +1,586 @@
+package cluster
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "golang.org/x/net/context"
+)
+
+// PublishingErrorHandler decides what to do with a publishing error in BatchingProducer
+type PublishingErrorHandler func(retries int, e error, batch *PubSubBatch) *PublishingErrorDecision
+
+type BatchingProducerConfig struct {
+ // Maximum size of the published batch. Default: 2000.
+ BatchSize int
+ // Max size of the requests waiting in queue. If value is provided, the producer will throw
+ // ProducerQueueFullException when queue size is exceeded. If 0 or unset, the queue is unbounded
+ // Note that bounded queue has better performance than unbounded queue.
+ // Default: 0 (unbounded)
+ MaxQueueSize int
+
+ // How long to wait for the publishing to complete.
+ // Default: 5s
+ PublishTimeout time.Duration
+
+ // Error handler that can decide what to do with an error when publishing a batch.
+ // Default: Fail and stop the BatchingProducer
+ OnPublishingError PublishingErrorHandler
+
+ // A throttle for logging from this producer. By default, a throttle shared between all instances of
+ // BatchingProducer is used, that allows for 10 events in 1 second.
+ LogThrottle actor.ShouldThrottle
+
+ // Optional idle timeout which will specify to the `IPublisher` how long it should wait before invoking clean
+ // up code to recover resources.
+ PublisherIdleTimeout time.Duration
+}
+
+var defaultBatchingProducerLogThrottle = actor.NewThrottle(10, time.Second, func(i int32) {
+ plog.Info("[BatchingProducer] Throttled logs", log.Int("count", int(i)))
+})
+
+func newBatchingProducerConfig(opts ...BatchingProducerConfigOption) *BatchingProducerConfig {
+ config := &BatchingProducerConfig{
+ BatchSize: 2000,
+ PublishTimeout: 5 * time.Second,
+ OnPublishingError: func(retries int, e error, batch *PubSubBatch) *PublishingErrorDecision {
+ return FailBatchAndStop
+ },
+ LogThrottle: defaultBatchingProducerLogThrottle,
+ }
+
+ for _, opt := range opts {
+ opt(config)
+ }
+
+ return config
+}
+
+type BatchingProducer struct {
+ config *BatchingProducerConfig
+ topic string
+ publisher Publisher
+ publisherChannel channel[produceMessage]
+ loopCancel context.CancelFunc
+ loopDone chan struct{}
+ msgLeft uint32
+}
+
+func NewBatchingProducer(publisher Publisher, topic string, opts ...BatchingProducerConfigOption) *BatchingProducer {
+ config := newBatchingProducerConfig(opts...)
+ p := &BatchingProducer{
+ config: config,
+ topic: topic,
+ publisher: publisher,
+ msgLeft: 0,
+ loopDone: make(chan struct{}),
+ }
+ if config.MaxQueueSize > 0 {
+ p.publisherChannel = newBoundedChannel[produceMessage](config.MaxQueueSize)
+ } else {
+ p.publisherChannel = newUnboundedChannel[produceMessage]()
+ }
+ ctx, cancelFunc := context.WithCancel(context.Background())
+ p.loopCancel = cancelFunc
+ go p.publishLoop(ctx)
+
+ return p
+}
+
+type pubsubBatchWithReceipts struct {
+ batch *PubSubBatch
+ ctxArr []context.Context
+}
+
+// newPubSubBatchWithReceipts creates a new pubsubBatchWithReceipts
+func newPubSubBatchWithReceipts() *pubsubBatchWithReceipts {
+ return &pubsubBatchWithReceipts{
+ batch: &PubSubBatch{Envelopes: make([]interface{}, 0, 10)},
+ ctxArr: make([]context.Context, 0, 10),
+ }
+}
+
+type produceMessage struct {
+ message interface{}
+ ctx context.Context
+}
+
+// Dispose stops the producer and releases all resources.
+func (p *BatchingProducer) Dispose() {
+ p.loopCancel()
+ p.publisherChannel.broadcast()
+ <-p.loopDone
+}
+
+// ProduceProcessInfo is the context for a Produce call
+type ProduceProcessInfo struct {
+ Finished chan struct{}
+ Err error
+ cancelFunc context.CancelFunc
+ cancelled chan struct{}
+}
+
+// IsCancelled returns true if the context has been cancelled
+func (p *ProduceProcessInfo) IsCancelled() bool {
+ select {
+ case <-p.cancelled:
+ return true
+ default:
+ return false
+ }
+}
+
+// IsFinished returns true if the context has been finished
+func (p *ProduceProcessInfo) IsFinished() bool {
+ select {
+ case <-p.Finished:
+ return true
+ default:
+ return false
+ }
+}
+
+// setErr sets the error for the ProduceProcessInfo
+func (p *ProduceProcessInfo) setErr(err error) {
+ p.Err = err
+ p.cancelFunc()
+ close(p.Finished)
+}
+
+// cancel the ProduceProcessInfo context
+func (p *ProduceProcessInfo) cancel() {
+ p.cancelFunc()
+ close(p.Finished)
+ close(p.cancelled)
+}
+
+// success closes the ProduceProcessInfo Finished channel
+func (p *ProduceProcessInfo) success() {
+ p.cancelFunc()
+ close(p.Finished)
+}
+
+type produceProcessInfoKey struct{}
+
+// GetProduceProcessInfo adds a new produce info to the BatchingProducer.Produce context
+func (p *BatchingProducer) getProduceProcessInfo(ctx context.Context) *ProduceProcessInfo {
+ return ctx.Value(produceProcessInfoKey{}).(*ProduceProcessInfo)
+}
+
+// Produce a message to producer queue. The return info can be used to wait for the message to be published.
+func (p *BatchingProducer) Produce(ctx context.Context, message interface{}) (*ProduceProcessInfo, error) {
+ ctx, cancel := context.WithCancel(ctx)
+ info := &ProduceProcessInfo{
+ Finished: make(chan struct{}),
+ cancelled: make(chan struct{}),
+ cancelFunc: cancel,
+ }
+ ctx = context.WithValue(ctx, produceProcessInfoKey{}, info)
+ if !p.publisherChannel.tryWrite(produceMessage{
+ message: message,
+ ctx: ctx,
+ }) {
+ if p.publisherChannel.isComplete() {
+ return info, &InvalidOperationException{Topic: p.topic}
+ }
+ return info, &ProducerQueueFullException{topic: p.topic}
+ }
+ return info, nil
+}
+
+// publishLoop is the main loop of the producer. It reads messages from the queue and publishes them in batches.
+func (p *BatchingProducer) publishLoop(ctx context.Context) {
+ defer close(p.loopDone)
+
+ plog.Debug("Producer is starting the publisher loop for topic", log.String("topic", p.topic))
+ batchWrapper := newPubSubBatchWithReceipts()
+
+ handleUnrecoverableError := func(err error) {
+ p.stopAcceptingNewMessages()
+ if p.config.LogThrottle() == actor.Open {
+ plog.Error("Error in the publisher loop of Producer for topic", log.String("topic", p.topic), log.Error(err))
+ }
+ p.failBatch(batchWrapper, err)
+ p.failPendingMessages(err)
+ }
+
+ _, err := p.publisher.Initialize(ctx, p.topic, PublisherConfig{IdleTimeout: p.config.PublisherIdleTimeout})
+ if err != nil && err != context.Canceled {
+ handleUnrecoverableError(err)
+ }
+
+loop:
+ for {
+ select {
+ case <-ctx.Done():
+ p.stopAcceptingNewMessages()
+ break loop
+ default:
+ if msg, ok := p.publisherChannel.tryRead(); ok {
+
+ // if msg ctx not done
+ select {
+ case <-msg.ctx.Done():
+ p.getProduceProcessInfo(msg.ctx).cancel()
+ default:
+ batchWrapper.batch.Envelopes = append(batchWrapper.batch.Envelopes, msg.message)
+ batchWrapper.ctxArr = append(batchWrapper.ctxArr, msg.ctx)
+ }
+
+ if len(batchWrapper.batch.Envelopes) < p.config.BatchSize {
+ continue
+ }
+
+ err := p.publishBatch(ctx, batchWrapper)
+ if err != nil {
+ handleUnrecoverableError(err)
+ break loop
+ }
+ batchWrapper = newPubSubBatchWithReceipts()
+ } else {
+ if len(batchWrapper.batch.Envelopes) > 0 {
+ err := p.publishBatch(ctx, batchWrapper)
+ if err != nil {
+ handleUnrecoverableError(err)
+ break loop
+ }
+ batchWrapper = newPubSubBatchWithReceipts()
+ }
+ p.publisherChannel.waitToRead()
+ }
+ }
+ }
+ p.cancelBatch(batchWrapper)
+ p.cancelPendingMessages()
+}
+
+// cancelPendingMessages cancels all pending messages
+func (p *BatchingProducer) cancelPendingMessages() {
+ for {
+ if msg, ok := p.publisherChannel.tryRead(); ok {
+ p.getProduceProcessInfo(msg.ctx).cancel()
+ } else {
+ break
+ }
+ }
+}
+
+// cancelBatch cancels all contexts in the batch wrapper
+func (p *BatchingProducer) cancelBatch(batchWrapper *pubsubBatchWithReceipts) {
+ for _, ctx := range batchWrapper.ctxArr {
+ p.getProduceProcessInfo(ctx).cancel()
+ }
+
+ // ensure once cancelled, we won't touch the batch anymore
+ p.clearBatch(batchWrapper)
+}
+
+// failPendingMessages fails all pending messages
+func (p *BatchingProducer) failPendingMessages(err error) {
+ for {
+ if msg, ok := p.publisherChannel.tryRead(); ok {
+ p.getProduceProcessInfo(msg.ctx).setErr(err)
+ } else {
+ break
+ }
+ }
+}
+
+// failBatch marks all contexts in the batch wrapper as failed
+func (p *BatchingProducer) failBatch(batchWrapper *pubsubBatchWithReceipts, err error) {
+ for _, ctx := range batchWrapper.ctxArr {
+ p.getProduceProcessInfo(ctx).setErr(err)
+ }
+
+ // ensure once failed, we won't touch the batch anymore
+ p.clearBatch(batchWrapper)
+}
+
+// clearBatch clears the batch wrapper
+func (p *BatchingProducer) clearBatch(batchWrapper *pubsubBatchWithReceipts) {
+ batchWrapper.batch = &PubSubBatch{Envelopes: make([]interface{}, 0, 10)}
+ batchWrapper.ctxArr = batchWrapper.ctxArr[:0]
+}
+
+// completeBatch marks all contexts in the batch wrapper as completed
+func (p *BatchingProducer) completeBatch(batchWrapper *pubsubBatchWithReceipts) {
+ for _, ctx := range batchWrapper.ctxArr {
+ p.getProduceProcessInfo(ctx).success()
+ }
+
+ // ensure once completed, we won't touch the batch anymore
+ p.clearBatch(batchWrapper)
+}
+
+// removeCancelledFromBatch removes all cancelled contexts from the batch wrapper
+func (p *BatchingProducer) removeCancelledFromBatch(batchWrapper *pubsubBatchWithReceipts) {
+ for i := len(batchWrapper.ctxArr) - 1; i >= 0; i-- {
+ select {
+ case <-batchWrapper.ctxArr[i].Done():
+ info := p.getProduceProcessInfo(batchWrapper.ctxArr[i])
+ select {
+ case <-info.Finished:
+ // if the message is already finished, we don't need to do anything
+ default:
+ info.cancel()
+ }
+
+ batchWrapper.batch.Envelopes = append(batchWrapper.batch.Envelopes[:i], batchWrapper.batch.Envelopes[i+1:]...)
+ batchWrapper.ctxArr = append(batchWrapper.ctxArr[:i], batchWrapper.ctxArr[i+1:]...)
+ default:
+ continue
+ }
+ }
+}
+
+// stopAcceptingNewMessages stops accepting new messages into the channel.
+func (p *BatchingProducer) stopAcceptingNewMessages() {
+ p.publisherChannel.complete()
+}
+
+// publishBatch publishes a batch of messages using Publisher.
+func (p *BatchingProducer) publishBatch(ctx context.Context, batchWrapper *pubsubBatchWithReceipts) error {
+ retries := 0
+ retry := true
+
+loop:
+ for retry {
+ select {
+ case <-ctx.Done():
+ p.cancelBatch(batchWrapper)
+ break loop
+ default:
+ retries++
+ _, err := p.publisher.PublishBatch(ctx, p.topic, batchWrapper.batch, WithTimeout(p.config.PublishTimeout))
+ if err != nil {
+ decision := p.config.OnPublishingError(retries, err, batchWrapper.batch)
+ if decision == FailBatchAndStop {
+ p.stopAcceptingNewMessages()
+ p.failBatch(batchWrapper, err)
+ return err // let the main producer loop exit
+ }
+
+ if p.config.LogThrottle() == actor.Open {
+ plog.Warn("Error while publishing batch", log.Error(err))
+ }
+
+ if decision == FailBatchAndContinue {
+ p.failBatch(batchWrapper, err)
+ return nil
+ }
+
+ // the decision is to retry
+ // if any of the messages have been canceled in the meantime, remove them and cancel the delivery report
+ p.removeCancelledFromBatch(batchWrapper)
+
+ if len(batchWrapper.batch.Envelopes) == 0 {
+ retry = false
+ } else if decision.Delay > 0 {
+ time.Sleep(decision.Delay)
+ }
+
+ continue
+ }
+
+ retry = false
+ p.completeBatch(batchWrapper)
+ }
+ }
+
+ return nil
+}
+
+type ProducerQueueFullException struct {
+ topic string
+}
+
+func (p *ProducerQueueFullException) Error() string {
+ return "Producer for topic " + p.topic + " has full queue"
+}
+
+func (p *ProducerQueueFullException) Is(target error) bool {
+ _, ok := target.(*ProducerQueueFullException)
+ return ok
+}
+
+type InvalidOperationException struct {
+ Topic string
+}
+
+func (i *InvalidOperationException) Is(err error) bool {
+ _, ok := err.(*InvalidOperationException)
+ return ok
+}
+
+func (i *InvalidOperationException) Error() string {
+ return "Producer for topic " + i.Topic + " is stopped, cannot produce more messages."
+}
+
+// channel is a wrapper around a channel that can be used to read and write messages.
+// messages must be pointers.
+type channel[T any] interface {
+ tryWrite(msg T) bool
+ tryRead() (T, bool)
+ isComplete() bool
+ complete()
+ empty() bool
+ waitToRead()
+ broadcast()
+}
+
+// BoundedChannel is a bounded channel with the given capacity.
+type boundedChannel[T any] struct {
+ capacity int
+ c chan T
+ quit chan struct{}
+ once *sync.Once
+ cond *sync.Cond
+ left *atomic.Bool
+}
+
+func (b *boundedChannel[T]) tryWrite(msg T) bool {
+ select {
+ case b.c <- msg:
+ b.cond.Broadcast()
+ return true
+ case <-b.quit:
+ return false
+ default:
+ return false
+ }
+}
+
+func (b *boundedChannel[T]) tryRead() (msg T, ok bool) {
+ var msgDefault T
+ select {
+ case msg, ok = <-b.c:
+ return
+ default:
+ return msgDefault, false
+ }
+}
+
+func (b *boundedChannel[T]) isComplete() bool {
+ select {
+ case <-b.quit:
+ return true
+ default:
+ return false
+ }
+}
+
+func (b *boundedChannel[T]) complete() {
+ b.once.Do(func() {
+ close(b.quit)
+ })
+}
+
+func (b *boundedChannel[T]) empty() bool {
+ return len(b.c) == 0
+}
+
+func (b *boundedChannel[T]) waitToRead() {
+ b.cond.L.Lock()
+ defer b.cond.L.Unlock()
+ for b.empty() && !b.left.Load() {
+ b.cond.Wait()
+ }
+ b.left.Store(false)
+}
+
+func (b *boundedChannel[T]) broadcast() {
+ b.left.Store(true)
+ b.cond.Broadcast()
+}
+
+// newBoundedChannel creates a new bounded channel with the given capacity.
+func newBoundedChannel[T any](capacity int) channel[T] {
+ return &boundedChannel[T]{
+ capacity: capacity,
+ c: make(chan T, capacity),
+ quit: make(chan struct{}),
+ cond: sync.NewCond(&sync.Mutex{}),
+ once: &sync.Once{},
+ left: &atomic.Bool{},
+ }
+}
+
+// UnboundedChannel is an unbounded channel.
+type unboundedChannel[T any] struct {
+ queue *mpsc.Queue
+ quit chan struct{}
+ once *sync.Once
+ cond *sync.Cond
+ left *atomic.Bool
+}
+
+func (u *unboundedChannel[T]) tryWrite(msg T) bool {
+ select {
+ case <-u.quit:
+ return false
+ default:
+ u.queue.Push(msg)
+ u.cond.Broadcast()
+ return true
+ }
+}
+
+func (u *unboundedChannel[T]) tryRead() (T, bool) {
+ var msg T
+ tmp := u.queue.Pop()
+ if tmp == nil {
+ return msg, false
+ } else {
+ u.cond.Broadcast()
+ return tmp.(T), true
+ }
+}
+
+func (u *unboundedChannel[T]) complete() {
+ u.once.Do(func() {
+ close(u.quit)
+ })
+}
+
+func (u *unboundedChannel[T]) isComplete() bool {
+ select {
+ case <-u.quit:
+ return true
+ default:
+ return false
+ }
+}
+
+func (u *unboundedChannel[T]) empty() bool {
+ return u.queue.Empty()
+}
+
+func (u *unboundedChannel[T]) waitToRead() {
+ u.cond.L.Lock()
+ defer u.cond.L.Unlock()
+ for u.empty() && !u.left.Load() {
+ u.cond.Wait()
+ }
+ u.left.Store(false)
+}
+
+func (u *unboundedChannel[T]) broadcast() {
+ u.left.Store(true)
+ u.cond.Broadcast()
+}
+
+// newUnboundedChannel creates a new unbounded channel.
+func newUnboundedChannel[T any]() channel[T] {
+ return &unboundedChannel[T]{
+ queue: mpsc.New(),
+ quit: make(chan struct{}),
+ cond: sync.NewCond(&sync.Mutex{}),
+ once: &sync.Once{},
+ left: &atomic.Bool{},
+ }
+}
diff --git a/cluster/pubsub_producer_opts.go b/cluster/pubsub_producer_opts.go
new file mode 100644
index 0000000000000000000000000000000000000000..af21282ab69c1590d1de82d8b19a47a0cc553b51
--- /dev/null
+++ b/cluster/pubsub_producer_opts.go
@@ -0,0 +1,82 @@
+package cluster
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type BatchingProducerConfigOption func(config *BatchingProducerConfig)
+
+// WithBatchingProducerBatchSize sets maximum size of the published batch. Default: 2000.
+func WithBatchingProducerBatchSize(batchSize int) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.BatchSize = batchSize
+ }
+}
+
+// WithBatchingProducerMaxQueueSize set max size of the requests waiting in queue. If value is provided, the producer will throw
+// ProducerQueueFullException when queue size is exceeded. If 0 or unset, the queue is unbounded
+// Note that bounded queue has better performance than unbounded queue.
+// Default: 0 (unbounded)
+func WithBatchingProducerMaxQueueSize(maxQueueSize int) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.MaxQueueSize = maxQueueSize
+ }
+}
+
+// WithBatchingProducerPublishTimeout sets how long to wait for the publishing to complete.
+// Default: 5s
+func WithBatchingProducerPublishTimeout(publishTimeout time.Duration) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.PublishTimeout = publishTimeout
+ }
+}
+
+// WithBatchingProducerOnPublishingError sets error handler that can decide what to do with an error when publishing a batch.
+// Default: Fail and stop the BatchingProducer
+func WithBatchingProducerOnPublishingError(onPublishingError PublishingErrorHandler) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.OnPublishingError = onPublishingError
+ }
+}
+
+// WithBatchingProducerLogThrottle sets a throttle for logging from this producer. By default, a throttle shared between all instances of
+// BatchingProducer is used, that allows for 10 events in 10 seconds.
+func WithBatchingProducerLogThrottle(logThrottle actor.ShouldThrottle) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.LogThrottle = logThrottle
+ }
+}
+
+// WithBatchingProducerPublisherIdleTimeout sets an optional idle timeout which will specify to the `IPublisher` how long it should wait before invoking clean
+// up code to recover resources.
+func WithBatchingProducerPublisherIdleTimeout(publisherIdleTimeout time.Duration) BatchingProducerConfigOption {
+ return func(config *BatchingProducerConfig) {
+ config.PublisherIdleTimeout = publisherIdleTimeout
+ }
+}
+
+type PublishingErrorDecision struct {
+ Delay time.Duration
+}
+
+// NewPublishingErrorDecision creates a new PublishingErrorDecision
+func NewPublishingErrorDecision(delay time.Duration) *PublishingErrorDecision {
+ return &PublishingErrorDecision{Delay: delay}
+}
+
+// RetryBatchAfter returns a new PublishingErrorDecision with the Delay set to the given duration
+func RetryBatchAfter(delay time.Duration) *PublishingErrorDecision {
+ return NewPublishingErrorDecision(delay)
+}
+
+// FailBatchAndStop causes the BatchingProducer to stop and fail the pending messages
+var FailBatchAndStop = NewPublishingErrorDecision(0)
+
+// FailBatchAndContinue skips the current batch and proceeds to the next one. The delivery reports (tasks) related to that batch are still
+// failed with the exception that triggered the error handling.
+var FailBatchAndContinue = NewPublishingErrorDecision(0)
+
+// RetryBatchImmediately retries the current batch immediately
+var RetryBatchImmediately = NewPublishingErrorDecision(0)
diff --git a/cluster/pubsub_producer_test.go b/cluster/pubsub_producer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..865c0f7572ad327e78f5bb68f1b3b019b3266b16
--- /dev/null
+++ b/cluster/pubsub_producer_test.go
@@ -0,0 +1,352 @@
+package cluster
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type PubSubBatchingProducerTestSuite struct {
+ suite.Suite
+ batchesSent []*PubSubBatch
+}
+
+func (suite *PubSubBatchingProducerTestSuite) SetupTest() {
+ suite.batchesSent = make([]*PubSubBatch, 0)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) allSentNumbersShouldEqual(batchesSent []*PubSubBatch, nums ...int) {
+ allNumbers := make([]int, 0, len(nums))
+ for _, batch := range batchesSent {
+ for _, envelope := range batch.Envelopes {
+ allNumbers = append(allNumbers, int(envelope.(*TestMessage).Number))
+ }
+ }
+ suite.Assert().ElementsMatch(nums, allNumbers)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) iter(from, to int) []int {
+ nums := make([]int, 0, to-from)
+ for i := from; i < to; i++ {
+ nums = append(nums, i)
+ }
+ return nums
+}
+
+func (suite *PubSubBatchingProducerTestSuite) record(batch *PubSubBatch) (*PublishResponse, error) {
+ b := &PubSubBatch{Envelopes: make([]interface{}, 0, len(batch.Envelopes))}
+ b.Envelopes = append(b.Envelopes, batch.Envelopes...)
+
+ suite.batchesSent = append(suite.batchesSent, b)
+ return &PublishResponse{Status: PublishStatus_Ok}, nil
+}
+
+func (suite *PubSubBatchingProducerTestSuite) wait(_ *PubSubBatch) (*PublishResponse, error) {
+ time.Sleep(time.Second * 1)
+ return &PublishResponse{Status: PublishStatus_Ok}, nil
+}
+
+func (suite *PubSubBatchingProducerTestSuite) waitThenFail(_ *PubSubBatch) (*PublishResponse, error) {
+ time.Sleep(time.Millisecond * 500)
+ return &PublishResponse{Status: PublishStatus_Failed}, &testException{}
+}
+
+func (suite *PubSubBatchingProducerTestSuite) fail(_ *PubSubBatch) (*PublishResponse, error) {
+ return &PublishResponse{Status: PublishStatus_Failed}, &testException{}
+}
+
+func (suite *PubSubBatchingProducerTestSuite) failTimesThenSucceed(times int) func(*PubSubBatch) (*PublishResponse, error) {
+ count := 0
+ return func(batch *PubSubBatch) (*PublishResponse, error) {
+ count++
+ if count <= times {
+ return &PublishResponse{Status: PublishStatus_Failed}, &testException{}
+ }
+ return suite.record(batch)
+ }
+}
+
+func (suite *PubSubBatchingProducerTestSuite) timeout() (*PublishResponse, error) {
+ return nil, nil
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestProducerSendsMessagesInBatches() {
+ producer := NewBatchingProducer(newMockPublisher(suite.record), "topic", WithBatchingProducerBatchSize(10))
+ defer producer.Dispose()
+
+ infos := make([]*ProduceProcessInfo, 0, 10000)
+ for i := 0; i < 10000; i++ {
+ info, err := producer.Produce(context.Background(), &TestMessage{Number: int32(i)})
+ suite.Assert().NoError(err)
+ infos = append(infos, info)
+ }
+ for _, info := range infos {
+ <-info.Finished
+ suite.Assert().Nil(info.Err)
+ }
+
+ anyBatchesEnvelopesCountIsGreaterThanOne := false
+ for _, batch := range suite.batchesSent {
+ if len(batch.Envelopes) > 1 {
+ anyBatchesEnvelopesCountIsGreaterThanOne = true
+ break
+ }
+ }
+ suite.Assert().True(anyBatchesEnvelopesCountIsGreaterThanOne, "messages should be batched")
+
+ allBatchesEnvelopeCountAreLessThanBatchSize := true
+ for _, batch := range suite.batchesSent {
+ if len(batch.Envelopes) > 10 {
+ allBatchesEnvelopeCountAreLessThanBatchSize = false
+ break
+ }
+ }
+ suite.Assert().True(allBatchesEnvelopeCountAreLessThanBatchSize, "batches should not exceed configured size")
+
+ suite.allSentNumbersShouldEqual(suite.batchesSent, suite.iter(0, 10000)...)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestPublishingThroughStoppedProducerThrows() {
+ producer := NewBatchingProducer(newMockPublisher(suite.record), "topic", WithBatchingProducerBatchSize(10))
+ producer.Dispose()
+
+ _, err := producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().ErrorIs(err, &InvalidOperationException{Topic: "topic"})
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestAllPendingTasksCompleteWhenProducerIsStopped() {
+ provider := NewBatchingProducer(newMockPublisher(suite.wait), "topic", WithBatchingProducerBatchSize(5))
+
+ infoList := make([]*ProduceProcessInfo, 0, 100)
+ for i := 0; i < 100; i++ {
+ info, err := provider.Produce(context.Background(), &TestMessage{Number: int32(i)})
+ suite.Assert().NoError(err)
+ infoList = append(infoList, info)
+ }
+
+ provider.Dispose()
+
+ for _, info := range infoList {
+ <-info.Finished
+ suite.Assert().Nil(info.Err)
+ }
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestAllPendingTasksCompleteWhenProducerFails() {
+ producer := NewBatchingProducer(newMockPublisher(suite.waitThenFail), "topic", WithBatchingProducerBatchSize(5))
+ defer producer.Dispose()
+
+ infoList := make([]*ProduceProcessInfo, 0, 100)
+ for i := 0; i < 100; i++ {
+ info, err := producer.Produce(context.Background(), &TestMessage{Number: int32(i)})
+ suite.Assert().NoError(err)
+ infoList = append(infoList, info)
+ }
+
+ for _, info := range infoList {
+ <-info.Finished
+ suite.Assert().Error(info.Err)
+ }
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestPublishingThroughFailedProducerThrows() {
+ producer := NewBatchingProducer(newMockPublisher(suite.fail), "topic", WithBatchingProducerBatchSize(10))
+ defer producer.Dispose()
+
+ info, err := producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().NoError(err)
+ <-info.Finished
+ suite.Assert().ErrorIs(info.Err, &testException{})
+
+ _, err = producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().ErrorIs(err, &InvalidOperationException{Topic: "topic"})
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestThrowsWhenQueueFull() {
+ producer := NewBatchingProducer(newMockPublisher(suite.record), "topic", WithBatchingProducerBatchSize(1), WithBatchingProducerMaxQueueSize(10))
+ defer producer.Dispose()
+
+ hasError := false
+ for i := 0; i < 20; i++ {
+ _, err := producer.Produce(context.Background(), &TestMessage{Number: int32(i)})
+ if err != nil {
+ hasError = true
+ suite.Assert().ErrorIs(err, &ProducerQueueFullException{})
+ }
+ }
+ suite.Assert().True(hasError)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestCanCancelPublishingAMessage() {
+ producer := NewBatchingProducer(newMockPublisher(suite.record), "topic", WithBatchingProducerBatchSize(1), WithBatchingProducerMaxQueueSize(10))
+ defer producer.Dispose()
+
+ messageWithoutCancellation := &TestMessage{Number: 1}
+ t1, err := producer.Produce(context.Background(), messageWithoutCancellation)
+ suite.Assert().NoError(err)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ t2, err := producer.Produce(ctx, &TestMessage{Number: 2})
+ cancel()
+ suite.Assert().NoError(err)
+
+ <-t1.Finished
+ suite.Assert().NoError(t1.Err)
+ <-t2.Finished
+ suite.Assert().True(t2.IsCancelled())
+
+ suite.allSentNumbersShouldEqual(suite.batchesSent, 1)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestCanRetryOnPublishingError() {
+ retries := make([]int, 0, 10)
+ producer := NewBatchingProducer(newMockPublisher(suite.failTimesThenSucceed(3)), "topic",
+ WithBatchingProducerBatchSize(1),
+ WithBatchingProducerOnPublishingError(func(retry int, e error, batch *PubSubBatch) *PublishingErrorDecision {
+ retries = append(retries, retry)
+ return RetryBatchImmediately
+ }))
+ defer producer.Dispose()
+
+ info, err := producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().NoError(err)
+
+ <-info.Finished
+ suite.Assert().Equal([]int{1, 2, 3}, retries)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestCanSkipBatchOnPublishingError() {
+ producer := NewBatchingProducer(newMockPublisher(suite.failTimesThenSucceed(1)), "topic",
+ WithBatchingProducerBatchSize(1),
+ WithBatchingProducerOnPublishingError(func(retry int, e error, batch *PubSubBatch) *PublishingErrorDecision {
+ return FailBatchAndContinue
+ }))
+ defer producer.Dispose()
+
+ t1, err := producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().NoError(err)
+
+ t2, err := producer.Produce(context.Background(), &TestMessage{Number: 2})
+ suite.Assert().NoError(err)
+
+ <-t1.Finished
+ suite.Assert().ErrorIs(t1.Err, &testException{})
+ <-t2.Finished
+ suite.Assert().NoError(t2.Err)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestCanStopProducerWhenRetryingInfinitely() {
+ producer := NewBatchingProducer(newMockPublisher(suite.fail), "topic",
+ WithBatchingProducerBatchSize(1),
+ WithBatchingProducerOnPublishingError(func(retry int, e error, batch *PubSubBatch) *PublishingErrorDecision {
+ return RetryBatchImmediately
+ }))
+
+ t1, err := producer.Produce(context.Background(), &TestMessage{Number: 1})
+ suite.Assert().NoError(err)
+
+ time.Sleep(50 * time.Millisecond)
+ producer.Dispose()
+ suite.Assert().True(t1.IsCancelled())
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestIfMessageIsCancelledMeanwhileRetryingItIsNotPublished() {
+ publisher := newOptionalFailureMockPublisher(true)
+ producer := NewBatchingProducer(publisher, "topic",
+ WithBatchingProducerBatchSize(1),
+ WithBatchingProducerOnPublishingError(func(retry int, e error, batch *PubSubBatch) *PublishingErrorDecision {
+ return RetryBatchImmediately
+ }))
+ defer producer.Dispose()
+ ctx, cancel := context.WithCancel(context.Background())
+ t1, err := producer.Produce(ctx, &TestMessage{Number: 1})
+ suite.Assert().NoError(err)
+
+ // give it a moment to spin
+ time.Sleep(50 * time.Millisecond)
+
+ // cancel the message publish
+ cancel()
+ <-t1.Finished
+ suite.Assert().True(t1.IsCancelled())
+
+ suite.Assert().Len(publisher.sentBatches, 0)
+ publisher.shouldFail = false
+ t2, err := producer.Produce(context.Background(), &TestMessage{Number: 2})
+ suite.Assert().NoError(err)
+ <-t2.Finished
+ suite.Assert().NoError(t2.Err)
+
+ suite.allSentNumbersShouldEqual(publisher.sentBatches, 2)
+}
+
+func (suite *PubSubBatchingProducerTestSuite) TestCanHandlePublishTimeouts() {
+}
+
+// In order for 'go test' to run this suite, we need to create
+// a normal test function and pass our suite to suite.Run
+func TestPubSubBatchingTestSuite(t *testing.T) {
+ suite.Run(t, new(PubSubBatchingProducerTestSuite))
+}
+
+type mockPublisher struct {
+ publish func(*PubSubBatch) (*PublishResponse, error)
+}
+
+func newMockPublisher(publish func(*PubSubBatch) (*PublishResponse, error)) *mockPublisher {
+ return &mockPublisher{publish: publish}
+}
+
+func (m *mockPublisher) Initialize(_ context.Context, topic string, config PublisherConfig) (*Acknowledge, error) {
+ return &Acknowledge{}, nil
+}
+
+func (m *mockPublisher) PublishBatch(_ context.Context, topic string, batch *PubSubBatch, opts ...GrainCallOption) (*PublishResponse, error) {
+ return m.publish(batch)
+}
+
+func (m *mockPublisher) Publish(_ context.Context, topic string, message interface{}, opts ...GrainCallOption) (*PublishResponse, error) {
+ return m.publish(&PubSubBatch{Envelopes: []interface{}{message}})
+}
+
+type optionalFailureMockPublisher struct {
+ sentBatches []*PubSubBatch
+ shouldFail bool
+}
+
+// newOptionalFailureMockPublisher creates a mock publisher that can be configured to fail or not
+func newOptionalFailureMockPublisher(shouldFail bool) *optionalFailureMockPublisher {
+ return &optionalFailureMockPublisher{shouldFail: shouldFail}
+}
+
+func (o *optionalFailureMockPublisher) Initialize(ctx context.Context, topic string, config PublisherConfig) (*Acknowledge, error) {
+ return &Acknowledge{}, nil
+}
+
+func (o *optionalFailureMockPublisher) PublishBatch(ctx context.Context, topic string, batch *PubSubBatch, opts ...GrainCallOption) (*PublishResponse, error) {
+ if o.shouldFail {
+ return nil, &testException{}
+ }
+ copiedBatch := &PubSubBatch{Envelopes: make([]interface{}, len(batch.Envelopes))}
+ copy(copiedBatch.Envelopes, batch.Envelopes)
+
+ o.sentBatches = append(o.sentBatches, copiedBatch)
+ return &PublishResponse{}, nil
+}
+
+func (o *optionalFailureMockPublisher) Publish(ctx context.Context, topic string, message interface{}, opts ...GrainCallOption) (*PublishResponse, error) {
+ return o.PublishBatch(ctx, topic, &PubSubBatch{Envelopes: []interface{}{message}}, opts...)
+}
+
+type testException struct{}
+
+func (t *testException) Error() string {
+ return "test exception"
+}
+
+func (t *testException) Is(err error) bool {
+ _, ok := err.(*testException)
+ return ok
+}
diff --git a/cluster/pubsub_publisher.go b/cluster/pubsub_publisher.go
new file mode 100644
index 0000000000000000000000000000000000000000..44e31a6c021ce8629b4c27b409382a43b3d2312e
--- /dev/null
+++ b/cluster/pubsub_publisher.go
@@ -0,0 +1,67 @@
+package cluster
+
+import (
+ "context"
+ "time"
+
+ "google.golang.org/protobuf/types/known/durationpb"
+)
+
+type PublisherConfig struct {
+ IdleTimeout time.Duration
+}
+
+type Publisher interface {
+ // Initialize the internal mechanisms of this publisher.
+ Initialize(ctx context.Context, topic string, config PublisherConfig) (*Acknowledge, error)
+
+ // PublishBatch publishes a batch of messages to the topic.
+ PublishBatch(ctx context.Context, topic string, batch *PubSubBatch, opts ...GrainCallOption) (*PublishResponse, error)
+
+ // Publish publishes a single message to the topic.
+ Publish(ctx context.Context, topic string, message interface{}, opts ...GrainCallOption) (*PublishResponse, error)
+}
+
+type defaultPublisher struct {
+ cluster *Cluster
+}
+
+func NewPublisher(cluster *Cluster) Publisher {
+ return &defaultPublisher{
+ cluster: cluster,
+ }
+}
+
+func (p *defaultPublisher) Initialize(ctx context.Context, topic string, config PublisherConfig) (*Acknowledge, error) {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ res, err := p.cluster.Call(topic, TopicActorKind, &Initialize{
+ IdleTimeout: durationpb.New(config.IdleTimeout),
+ })
+ if err != nil {
+ return nil, err
+ }
+ return res.(*Acknowledge), err
+ }
+}
+
+func (p *defaultPublisher) PublishBatch(ctx context.Context, topic string, batch *PubSubBatch, opts ...GrainCallOption) (*PublishResponse, error) {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ res, err := p.cluster.Call(topic, TopicActorKind, batch, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return res.(*PublishResponse), err
+ }
+}
+
+func (p *defaultPublisher) Publish(ctx context.Context, topic string, message interface{}, opts ...GrainCallOption) (*PublishResponse, error) {
+ return p.PublishBatch(ctx, topic, &PubSubBatch{
+ Envelopes: []interface{}{message},
+ }, opts...)
+}
diff --git a/cluster/pubsub_test.pb.go b/cluster/pubsub_test.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..62f0ac87451d773e23d35acf33178f8b0b4167b0
--- /dev/null
+++ b/cluster/pubsub_test.pb.go
@@ -0,0 +1,144 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.28.1
+// protoc v3.21.9
+// source: pubsub_test.proto
+
+package cluster
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type TestMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
+}
+
+func (x *TestMessage) Reset() {
+ *x = TestMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_pubsub_test_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *TestMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TestMessage) ProtoMessage() {}
+
+func (x *TestMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_pubsub_test_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use TestMessage.ProtoReflect.Descriptor instead.
+func (*TestMessage) Descriptor() ([]byte, []int) {
+ return file_pubsub_test_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *TestMessage) GetNumber() int32 {
+ if x != nil {
+ return x.Number
+ }
+ return 0
+}
+
+var File_pubsub_test_proto protoreflect.FileDescriptor
+
+var file_pubsub_test_proto_rawDesc = []byte{
+ 0x0a, 0x11, 0x70, 0x75, 0x62, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x22, 0x25, 0x0a, 0x0b,
+ 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e,
+ 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d,
+ 0x62, 0x65, 0x72, 0x42, 0x2c, 0x5a, 0x2a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65,
+ 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_pubsub_test_proto_rawDescOnce sync.Once
+ file_pubsub_test_proto_rawDescData = file_pubsub_test_proto_rawDesc
+)
+
+func file_pubsub_test_proto_rawDescGZIP() []byte {
+ file_pubsub_test_proto_rawDescOnce.Do(func() {
+ file_pubsub_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_pubsub_test_proto_rawDescData)
+ })
+ return file_pubsub_test_proto_rawDescData
+}
+
+var file_pubsub_test_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_pubsub_test_proto_goTypes = []interface{}{
+ (*TestMessage)(nil), // 0: cluster.TestMessage
+}
+var file_pubsub_test_proto_depIdxs = []int32{
+ 0, // [0:0] is the sub-list for method output_type
+ 0, // [0:0] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_pubsub_test_proto_init() }
+func file_pubsub_test_proto_init() {
+ if File_pubsub_test_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_pubsub_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*TestMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_pubsub_test_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 1,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_pubsub_test_proto_goTypes,
+ DependencyIndexes: file_pubsub_test_proto_depIdxs,
+ MessageInfos: file_pubsub_test_proto_msgTypes,
+ }.Build()
+ File_pubsub_test_proto = out.File
+ file_pubsub_test_proto_rawDesc = nil
+ file_pubsub_test_proto_goTypes = nil
+ file_pubsub_test_proto_depIdxs = nil
+}
diff --git a/cluster/pubsub_test.proto b/cluster/pubsub_test.proto
new file mode 100644
index 0000000000000000000000000000000000000000..76ed34c6cf146d1d602760871b6a426ec40bacba
--- /dev/null
+++ b/cluster/pubsub_test.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+package cluster;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/cluster";
+
+message TestMessage {
+ int32 number = 1;
+}
diff --git a/cluster/pubsub_topic.go b/cluster/pubsub_topic.go
new file mode 100644
index 0000000000000000000000000000000000000000..bed2ddfb5c4d03e54162ebb4a1f03e17e75c64d8
--- /dev/null
+++ b/cluster/pubsub_topic.go
@@ -0,0 +1,360 @@
+package cluster
+
+import (
+ "context"
+ "strings"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "golang.org/x/exp/maps"
+)
+
+const TopicActorKind = "prototopic"
+
+var topicLogThrottle = actor.NewThrottle(10, time.Second, func(count int32) {
+ plog.Info("[TopicActor] Throttled logs", log.Int("count", int(count)))
+})
+
+type TopicActor struct {
+ topic string
+ subscribers map[subscribeIdentityStruct]*SubscriberIdentity
+ subscriptionStore KeyValueStore[*Subscribers]
+ topologySubscription *eventstream.Subscription
+}
+
+func NewTopicActor(store KeyValueStore[*Subscribers]) *TopicActor {
+ return &TopicActor{
+ subscriptionStore: store,
+ subscribers: make(map[subscribeIdentityStruct]*SubscriberIdentity),
+ }
+}
+
+func (t *TopicActor) Receive(c actor.Context) {
+ switch msg := c.Message().(type) {
+ case *actor.Started:
+ t.onStarted(c)
+ case *actor.Stopping:
+ t.onStopping(c)
+ case *actor.ReceiveTimeout:
+ t.onReceiveTimeout(c)
+ case *Initialize:
+ t.onInitialize(c, msg)
+ case *SubscribeRequest:
+ t.onSubscribe(c, msg)
+ case *UnsubscribeRequest:
+ t.onUnsubscribe(c, msg)
+ case *PubSubBatch:
+ t.onPubSubBatch(c, msg)
+ case *NotifyAboutFailingSubscribersRequest:
+ t.onNotifyAboutFailingSubscribers(c, msg)
+ case *ClusterTopology:
+ t.onClusterTopologyChanged(c, msg)
+ }
+}
+
+func (t *TopicActor) onStarted(c actor.Context) {
+ t.topic = GetClusterIdentity(c).Identity
+ t.topologySubscription = c.ActorSystem().EventStream.Subscribe(func(evt interface{}) {
+ if clusterTopology, ok := evt.(*ClusterTopology); ok {
+ c.Send(c.Self(), clusterTopology)
+ }
+ })
+
+ sub := t.loadSubscriptions(t.topic)
+ if sub.Subscribers != nil {
+ for _, subscriber := range sub.Subscribers {
+ t.subscribers[newSubscribeIdentityStruct(subscriber)] = subscriber
+ }
+ }
+ t.unsubscribeSubscribersOnMembersThatLeft(c)
+
+ plog.Debug("Topic started", log.String("topic", t.topic))
+}
+
+func (t *TopicActor) onStopping(c actor.Context) {
+ if t.topologySubscription != nil {
+ c.ActorSystem().EventStream.Unsubscribe(t.topologySubscription)
+ t.topologySubscription = nil
+ }
+}
+
+func (t *TopicActor) onReceiveTimeout(c actor.Context) {
+ c.Stop(c.Self())
+}
+
+func (t *TopicActor) onInitialize(c actor.Context, msg *Initialize) {
+ if msg.IdleTimeout != nil {
+ duration := msg.IdleTimeout.AsDuration()
+ if duration > 0 {
+ c.SetReceiveTimeout(duration)
+ }
+ }
+ c.Respond(&Acknowledge{})
+}
+
+type pidAndSubscriber struct {
+ pid *actor.PID
+ subscriber *SubscriberIdentity
+}
+
+// onPubSubBatch handles a PubSubBatch message, sends the message to all subscribers
+func (t *TopicActor) onPubSubBatch(c actor.Context, batch *PubSubBatch) {
+ // map subscribers to map[address][](pid, subscriber)
+ members := make(map[string][]pidAndSubscriber)
+ for _, identity := range t.subscribers {
+ pid := t.getPID(c, identity)
+ if pid != nil {
+ members[pid.Address] = append(members[pid.Address], pidAndSubscriber{pid: pid, subscriber: identity})
+ }
+ }
+
+ // send message to each member
+ for address, member := range members {
+ subscribersOnMember := t.getSubscribersForAddress(member)
+ deliveryMessage := &DeliverBatchRequest{
+ Subscribers: subscribersOnMember,
+ PubSubBatch: batch,
+ Topic: t.topic,
+ }
+ deliveryPid := actor.NewPID(address, PubSubDeliveryName)
+ c.Send(deliveryPid, deliveryMessage)
+ }
+ c.Respond(&PublishResponse{})
+}
+
+// getSubscribersForAddress returns the subscribers for the given member list
+func (t *TopicActor) getSubscribersForAddress(members []pidAndSubscriber) *Subscribers {
+ subscribers := make([]*SubscriberIdentity, len(members))
+ for i, member := range members {
+ subscribers[i] = member.subscriber
+ }
+ return &Subscribers{Subscribers: subscribers}
+}
+
+// getPID returns the PID of the subscriber
+func (t *TopicActor) getPID(c actor.Context, subscriber *SubscriberIdentity) *actor.PID {
+ if pid := subscriber.GetPid(); pid != nil {
+ return pid
+ }
+
+ return t.getClusterIdentityPid(c, subscriber.GetClusterIdentity())
+}
+
+// getClusterIdentityPid returns the PID of the clusterIdentity actor
+func (t *TopicActor) getClusterIdentityPid(c actor.Context, identity *ClusterIdentity) *actor.PID {
+ if identity == nil {
+ return nil
+ }
+
+ return GetCluster(c.ActorSystem()).Get(identity.Identity, identity.Kind)
+}
+
+// onNotifyAboutFailingSubscribers handles a NotifyAboutFailingSubscribersRequest message
+func (t *TopicActor) onNotifyAboutFailingSubscribers(c actor.Context, msg *NotifyAboutFailingSubscribersRequest) {
+ t.unsubscribeUnreachablePidSubscribers(c, msg.InvalidDeliveries)
+ t.logDeliveryErrors(msg.InvalidDeliveries)
+ c.Respond(&NotifyAboutFailingSubscribersResponse{})
+}
+
+// logDeliveryErrors logs the delivery errors in one log line
+func (t *TopicActor) logDeliveryErrors(reports []*SubscriberDeliveryReport) {
+ if len(reports) > 0 || topicLogThrottle() == actor.Open {
+ subscribers := make([]string, len(reports))
+ for i, report := range reports {
+ subscribers[i] = report.Subscriber.String()
+ }
+ plog.Error("Topic following subscribers could not process the batch", log.String("topic", t.topic), log.String("subscribers", strings.Join(subscribers, ",")))
+ }
+}
+
+// unsubscribeUnreachablePidSubscribers deletes all subscribers that have a PID that is unreachable
+func (t *TopicActor) unsubscribeUnreachablePidSubscribers(_ actor.Context, allInvalidDeliveryReports []*SubscriberDeliveryReport) {
+ subscribers := make([]subscribeIdentityStruct, 0, len(allInvalidDeliveryReports))
+ for _, r := range allInvalidDeliveryReports {
+ if r.Subscriber.GetPid() != nil && r.Status == DeliveryStatus_SubscriberNoLongerReachable {
+ subscribers = append(subscribers, newSubscribeIdentityStruct(r.Subscriber))
+ }
+ }
+ t.removeSubscribers(subscribers)
+}
+
+// onClusterTopologyChanged handles a ClusterTopology message
+func (t *TopicActor) onClusterTopologyChanged(_ actor.Context, msg *ClusterTopology) {
+ if len(msg.Left) > 0 {
+ addressMap := make(map[string]struct{})
+ for _, member := range msg.Left {
+ addressMap[member.Address()] = struct{}{}
+ }
+
+ subscribersThatLeft := make([]subscribeIdentityStruct, 0, len(msg.Left))
+
+ for identityStruct, identity := range t.subscribers {
+ if pid := identity.GetPid(); pid != nil {
+ if _, ok := addressMap[pid.Address]; ok {
+ subscribersThatLeft = append(subscribersThatLeft, identityStruct)
+ }
+ }
+ }
+ t.removeSubscribers(subscribersThatLeft)
+ }
+}
+
+// unsubscribeSubscribersOnMembersThatLeft removes subscribers that are on members that left the clusterIdentity
+func (t *TopicActor) unsubscribeSubscribersOnMembersThatLeft(c actor.Context) {
+ members := GetCluster(c.ActorSystem()).MemberList.Members()
+ activeMemberAddresses := make(map[string]struct{})
+ for _, member := range members.Members() {
+ activeMemberAddresses[member.Address()] = struct{}{}
+ }
+
+ subscribersThatLeft := make([]subscribeIdentityStruct, 0)
+ for s := range t.subscribers {
+ if s.isPID {
+ if _, ok := activeMemberAddresses[s.pid.address]; !ok {
+ subscribersThatLeft = append(subscribersThatLeft, s)
+ }
+ }
+ }
+ t.removeSubscribers(subscribersThatLeft)
+}
+
+// removeSubscribers remove subscribers from the topic
+func (t *TopicActor) removeSubscribers(subscribersThatLeft []subscribeIdentityStruct) {
+ if len(subscribersThatLeft) > 0 {
+ for _, subscriber := range subscribersThatLeft {
+ delete(t.subscribers, subscriber)
+ }
+ if topicLogThrottle() == actor.Open {
+ plog.Warn("Topic removed subscribers, because they are dead or they are on members that left the clusterIdentity:", log.String("topic", t.topic), log.Object("subscribers", subscribersThatLeft))
+ }
+ t.saveSubscriptionsInTopicActor()
+ }
+}
+
+// loadSubscriptions loads the subscriptions for the topic from the subscription store
+func (t *TopicActor) loadSubscriptions(topic string) *Subscribers {
+ // TODO: cancellation logic config?
+ state, err := t.subscriptionStore.Get(context.Background(), topic)
+ if err != nil {
+ if topicLogThrottle() == actor.Open {
+ plog.Error("Error when loading subscriptions", log.String("topic", topic), log.Error(err))
+ }
+ return &Subscribers{}
+ }
+ if state == nil {
+ return &Subscribers{}
+ }
+ plog.Debug("Loaded subscriptions for topic", log.String("topic", topic), log.Object("subscriptions", state))
+ return state
+}
+
+// saveSubscriptionsInTopicActor saves the TopicActor.subscribers for the TopicActor.topic to the subscription store
+func (t *TopicActor) saveSubscriptionsInTopicActor() {
+ t.saveSubscriptions(t.topic, &Subscribers{Subscribers: maps.Values(t.subscribers)})
+}
+
+// saveSubscriptions saves the subscribers for the topic to the subscription store
+func (t *TopicActor) saveSubscriptions(topic string, subscribers *Subscribers) {
+ // TODO: cancellation logic config?
+ plog.Debug("Saving subscriptions for topic", log.String("topic", topic), log.Object("subscriptions", subscribers))
+ err := t.subscriptionStore.Set(context.Background(), topic, subscribers)
+ if err != nil && topicLogThrottle() == actor.Open {
+ plog.Error("Error when saving subscriptions", log.String("topic", topic), log.Error(err))
+ }
+}
+
+func (t *TopicActor) onUnsubscribe(c actor.Context, msg *UnsubscribeRequest) {
+ delete(t.subscribers, newSubscribeIdentityStruct(msg.Subscriber))
+ t.saveSubscriptionsInTopicActor()
+ c.Respond(&UnsubscribeResponse{})
+}
+
+func (t *TopicActor) onSubscribe(c actor.Context, msg *SubscribeRequest) {
+ t.subscribers[newSubscribeIdentityStruct(msg.Subscriber)] = msg.Subscriber
+ plog.Debug("Topic subscribed", log.String("topic", t.topic), log.Object("subscriber", msg.Subscriber))
+ t.saveSubscriptionsInTopicActor()
+ c.Respond(&SubscribeResponse{})
+}
+
+// pidStruct is a struct that represents a PID
+// It is used to implement the comparison interface
+type pidStruct struct {
+ address string
+ id string
+ requestId uint32
+}
+
+// newPIDStruct creates a new pidStruct from a *actor.PID
+func newPidStruct(pid *actor.PID) pidStruct {
+ return pidStruct{
+ address: pid.Address,
+ id: pid.Id,
+ requestId: pid.RequestId,
+ }
+}
+
+// toPID converts a pidStruct to a *actor.PID
+func (p pidStruct) toPID() *actor.PID {
+ return &actor.PID{
+ Address: p.address,
+ Id: p.id,
+ RequestId: p.requestId,
+ }
+}
+
+type clusterIdentityStruct struct {
+ identity string
+ kind string
+}
+
+// newClusterIdentityStruct creates a new clusterIdentityStruct from a *ClusterIdentity
+func newClusterIdentityStruct(clusterIdentity *ClusterIdentity) clusterIdentityStruct {
+ return clusterIdentityStruct{
+ identity: clusterIdentity.Identity,
+ kind: clusterIdentity.Kind,
+ }
+}
+
+// toClusterIdentity converts a clusterIdentityStruct to a *ClusterIdentity
+func (c clusterIdentityStruct) toClusterIdentity() *ClusterIdentity {
+ return &ClusterIdentity{
+ Identity: c.identity,
+ Kind: c.kind,
+ }
+}
+
+// subscriberIdentityStruct is a struct that represents a SubscriberIdentity
+// It is used to implement the comparison interface
+type subscribeIdentityStruct struct {
+ isPID bool
+ pid pidStruct
+ clusterIdentity clusterIdentityStruct
+}
+
+// newSubscriberIdentityStruct creates a new subscriberIdentityStruct from a *SubscriberIdentity
+func newSubscribeIdentityStruct(subscriberIdentity *SubscriberIdentity) subscribeIdentityStruct {
+ if subscriberIdentity.GetPid() != nil {
+ return subscribeIdentityStruct{
+ isPID: true,
+ pid: newPidStruct(subscriberIdentity.GetPid()),
+ }
+ }
+ return subscribeIdentityStruct{
+ isPID: false,
+ clusterIdentity: newClusterIdentityStruct(subscriberIdentity.GetClusterIdentity()),
+ }
+}
+
+// toSubscriberIdentity converts a subscribeIdentityStruct to a *SubscriberIdentity
+func (s subscribeIdentityStruct) toSubscriberIdentity() *SubscriberIdentity {
+ if s.isPID {
+ return &SubscriberIdentity{
+ Identity: &SubscriberIdentity_Pid{Pid: s.pid.toPID()},
+ }
+ }
+ return &SubscriberIdentity{
+ Identity: &SubscriberIdentity_ClusterIdentity{ClusterIdentity: s.clusterIdentity.toClusterIdentity()},
+ }
+}
diff --git a/cluster/rendezvous.go b/cluster/rendezvous.go
new file mode 100644
index 0000000000000000000000000000000000000000..be58be8b6ba18ec31a1b433a59f442af86889528
--- /dev/null
+++ b/cluster/rendezvous.go
@@ -0,0 +1,112 @@
+package cluster
+
+// Rendezvous.go
+// A revised FNV1A32 version of
+// https://github.com/tysonmote/rendezvous/blob/master/rendezvous.go
+
+import (
+ "hash"
+ "hash/fnv"
+ "strings"
+ "sync"
+)
+
+type memberData struct {
+ member *Member
+ hashBytes []byte
+}
+type Rendezvous struct {
+ mutex sync.RWMutex
+ hasher hash.Hash32
+ hasherLock sync.Mutex
+ members []*memberData
+}
+
+func NewRendezvous() *Rendezvous {
+ return &Rendezvous{
+ hasher: fnv.New32a(),
+ members: make([]*memberData, 0),
+ }
+}
+
+func (r *Rendezvous) GetByClusterIdentity(ci *ClusterIdentity) string {
+ r.mutex.RLock()
+ defer r.mutex.RUnlock()
+
+ identity := ci.Identity
+ m := r.memberDataByKind(ci.Kind)
+
+ l := len(m)
+
+ if l == 0 {
+ return ""
+ }
+
+ if l == 1 {
+ return m[0].member.Address()
+ }
+
+ keyBytes := []byte(identity)
+
+ var maxScore uint32
+ var maxMember *memberData
+ var score uint32
+
+ for _, node := range m {
+ score = r.hash(node.hashBytes, keyBytes)
+ if score > maxScore {
+ maxScore = score
+ maxMember = node
+ }
+ }
+
+ if maxMember == nil {
+ return ""
+ }
+ return maxMember.member.Address()
+}
+
+func (r *Rendezvous) GetByIdentity(identity string) string {
+ parts := strings.SplitN(identity, "/", 2)
+
+ return r.GetByClusterIdentity(&ClusterIdentity{
+ Kind: parts[0],
+ Identity: parts[1],
+ })
+}
+
+func (r *Rendezvous) memberDataByKind(kind string) []*memberData {
+ m := make([]*memberData, 0)
+ for _, md := range r.members {
+ if md.member.HasKind(kind) {
+ m = append(m, md)
+ }
+ }
+ return m
+}
+
+func (r *Rendezvous) UpdateMembers(members Members) {
+ r.mutex.Lock()
+ defer r.mutex.Unlock()
+
+ tmp := members.ToSet()
+ r.members = make([]*memberData, 0)
+
+ for _, m := range tmp.Members() {
+ keyBytes := []byte(m.Address()) // TODO: should be utf8 to match .net
+ r.members = append(r.members, &memberData{
+ member: m,
+ hashBytes: keyBytes,
+ })
+ }
+}
+
+func (r *Rendezvous) hash(node, key []byte) uint32 {
+ r.hasherLock.Lock()
+ defer r.hasherLock.Unlock()
+
+ r.hasher.Reset()
+ r.hasher.Write(key)
+ r.hasher.Write(node)
+ return r.hasher.Sum32()
+}
diff --git a/cluster/rendezvous_test.go b/cluster/rendezvous_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2b7ee04616eb4d8678f161dbe25b26350f2b17e5
--- /dev/null
+++ b/cluster/rendezvous_test.go
@@ -0,0 +1,32 @@
+package cluster
+
+import (
+ "fmt"
+ "runtime"
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+func Benchmark_Rendezvous_Get(b *testing.B) {
+ SetLogLevel(log.ErrorLevel)
+ for _, v := range []int{1, 2, 3, 5, 10, 100, 1000, 2000} {
+ members := newMembersForTest(v)
+ ms := newDefaultMemberStrategy(nil, "kind").(*simpleMemberStrategy)
+ for _, member := range members {
+ ms.AddMember(member)
+ }
+ obj := NewRendezvous()
+ obj.UpdateMembers(members)
+ testName := fmt.Sprintf("member*%d", v)
+ runtime.GC()
+ b.Run(testName, func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ address := obj.GetByIdentity("kind/0123456789abcdefghijklmnopqrstuvwxyz")
+ if address == "" {
+ b.Fatalf("empty address res=%d", len(members))
+ }
+ }
+ })
+ }
+}
diff --git a/cluster/round_robin.go b/cluster/round_robin.go
new file mode 100644
index 0000000000000000000000000000000000000000..05093b06c543f0b244a274a2a5d78587fdf327b3
--- /dev/null
+++ b/cluster/round_robin.go
@@ -0,0 +1,25 @@
+package cluster
+
+import "sync/atomic"
+
+type SimpleRoundRobin struct {
+ val int32
+ m MemberStrategy
+}
+
+func NewSimpleRoundRobin(memberStrategy MemberStrategy) *SimpleRoundRobin {
+ return &SimpleRoundRobin{m: memberStrategy}
+}
+
+func (r *SimpleRoundRobin) GetByRoundRobin() string {
+ members := r.m.GetAllMembers()
+ l := len(members)
+ if l == 0 {
+ return ""
+ }
+ if l == 1 {
+ return members[0].Address()
+ }
+ nv := atomic.AddInt32(&r.val, 1)
+ return members[int(nv)%l].Address()
+}
diff --git a/cluster/types.go b/cluster/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..ebc370b0b2168983d158b2983d817e72c46a9131
--- /dev/null
+++ b/cluster/types.go
@@ -0,0 +1,5 @@
+package cluster
+
+type GossipMemberStates = map[string]*GossipMemberState
+
+type GossipKeyValues = map[string]*GossipKeyValue
diff --git a/ctxext/extensions.go b/ctxext/extensions.go
new file mode 100644
index 0000000000000000000000000000000000000000..37725e191209a0adb7f9cd6af7c8514057eefc13
--- /dev/null
+++ b/ctxext/extensions.go
@@ -0,0 +1,42 @@
+package ctxext
+
+import "sync/atomic"
+
+type ContextExtensionID int32
+
+var currentContextExtensionID int32
+
+type ContextExtension interface {
+ ExtensionID() ContextExtensionID
+}
+
+type ContextExtensions struct {
+ extensions []ContextExtension
+}
+
+func NewContextExtensions() *ContextExtensions {
+ ex := &ContextExtensions{
+ extensions: make([]ContextExtension, 3),
+ }
+
+ return ex
+}
+
+func NextContextExtensionID() ContextExtensionID {
+ id := atomic.AddInt32(¤tContextExtensionID, 1)
+ return ContextExtensionID(id)
+}
+
+func (ex *ContextExtensions) Get(id ContextExtensionID) ContextExtension {
+ return ex.extensions[id]
+}
+
+func (ex *ContextExtensions) Set(extension ContextExtension) {
+ id := int32(extension.ExtensionID())
+ if id >= int32(len(ex.extensions)) {
+ newExtensions := make([]ContextExtension, id*2)
+ copy(newExtensions, ex.extensions)
+ ex.extensions = newExtensions
+ }
+ ex.extensions[id] = extension
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9aaf3fe0294dec81d0e202263a9581b087ef63c0
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,39 @@
+# docker-compose up -d
+# @see https://docs.docker.com/compose/compose-file/
+version: '3.4'
+services:
+ consul:
+ container_name: dev-consul
+ image: consul:1.10
+ restart: unless-stopped
+ ports:
+ - 8500:8500
+ - 8600:8600/udp
+ command: agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
+
+
+ etcd:
+ container_name: dev-etcd
+ image: quay.io/coreos/etcd:v3.5.0
+ restart: unless-stopped
+ ports:
+ - 2379:2379
+ - 2380:2380
+ command: >
+ /usr/local/bin/etcd
+ --name node1
+ --data-dir=/etcd-data
+ --listen-client-urls http://0.0.0.0:2379
+ --listen-peer-urls http://0.0.0.0:2380
+ --advertise-client-urls http://127.0.0.1:2379
+ --initial-advertise-peer-urls http://127.0.0.1:2380
+ --initial-cluster node1=http://127.0.0.1:2380
+ --auto-compaction-mode=periodic
+ --auto-compaction-retention=30m
+
+ zookeeper:
+ container_name: dev-zookeeper
+ image: zookeeper:3.7
+ restart: unless-stopped
+ ports:
+ - 8000:2181
diff --git a/eventstream/doc.go b/eventstream/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..d28e206faa699253d392ccee0633ba98debf32c5
--- /dev/null
+++ b/eventstream/doc.go
@@ -0,0 +1,4 @@
+/*
+Package eventstream implements a publisher / subscriber.
+*/
+package eventstream
diff --git a/eventstream/eventstream.go b/eventstream/eventstream.go
new file mode 100644
index 0000000000000000000000000000000000000000..8fca317ae82fc518b73acad93e379c16cc647060
--- /dev/null
+++ b/eventstream/eventstream.go
@@ -0,0 +1,145 @@
+package eventstream
+
+import (
+ "sync"
+ "sync/atomic"
+)
+
+// Handler defines a callback function that must be pass when subscribing.
+type Handler func(interface{})
+
+// Predicate is a function used to filter messages before being forwarded to a subscriber
+type Predicate func(evt interface{}) bool
+
+type EventStream struct {
+ sync.RWMutex
+
+ // slice containing our subscriptions
+ subscriptions []*Subscription
+
+ // Atomically maintained elements counter
+ counter int32
+}
+
+// Create a new EventStream value and returns it back.
+func NewEventStream() *EventStream {
+ es := &EventStream{
+ subscriptions: []*Subscription{},
+ }
+
+ return es
+}
+
+// Subscribe the given handler to the EventStream
+func (es *EventStream) Subscribe(handler Handler) *Subscription {
+ sub := &Subscription{
+ handler: handler,
+ active: 1,
+ }
+
+ es.Lock()
+ defer es.Unlock()
+
+ sub.id = es.counter
+ es.counter++
+ es.subscriptions = append(es.subscriptions, sub)
+
+ return sub
+}
+
+// SubscribeWithPredicate creates a new Subscription value and sets a predicate to filter messages passed to
+// the subscriber, it returns a pointer to the Subscription value
+func (es *EventStream) SubscribeWithPredicate(handler Handler, p Predicate) *Subscription {
+ sub := es.Subscribe(handler)
+ sub.p = p
+
+ return sub
+}
+
+// Unsubscribes the given subscription from the EventStream
+func (es *EventStream) Unsubscribe(sub *Subscription) {
+ if sub == nil {
+ return
+ }
+
+ if sub.IsActive() {
+ es.Lock()
+ defer es.Unlock()
+
+ if sub.Deactivate() {
+ if es.counter == 0 {
+ es.subscriptions = nil
+
+ return
+ }
+
+ l := es.counter - 1
+ es.subscriptions[sub.id] = es.subscriptions[l]
+ es.subscriptions[sub.id].id = sub.id
+ es.subscriptions[l] = nil
+ es.subscriptions = es.subscriptions[:l]
+ es.counter--
+
+ if es.counter == 0 {
+ es.subscriptions = nil
+ }
+ }
+ }
+}
+
+// Publishes the given event to all the subscribers in the stream
+func (es *EventStream) Publish(evt interface{}) {
+ subs := make([]*Subscription, 0, es.Length())
+ es.RLock()
+ for _, sub := range es.subscriptions {
+ if sub.IsActive() {
+ subs = append(subs, sub)
+ }
+ }
+ es.RUnlock()
+
+ for _, sub := range subs {
+ // there is a subscription predicate and it didn't pass, return
+ if sub.p != nil && !sub.p(evt) {
+ continue
+ }
+
+ // finally here, lets execute our handler
+ sub.handler(evt)
+ }
+}
+
+// Returns an integer that represents the current number of subscribers to the stream
+func (es *EventStream) Length() int32 {
+ es.RLock()
+ defer es.RUnlock()
+ return es.counter
+}
+
+// Subscription is returned from the Subscribe function.
+//
+// This value and can be passed to Unsubscribe when the observer is no longer interested in receiving messages
+type Subscription struct {
+ id int32
+ handler Handler
+ p Predicate
+ active uint32
+}
+
+// Activates the Subscription setting its active flag as 1, if the subscription
+// was already active it returns false, true otherwise
+func (s *Subscription) Activate() bool {
+ return atomic.CompareAndSwapUint32(&s.active, 0, 1)
+}
+
+// Deactivates the Subscription setting its active flag as 0, if the subscription
+// was already inactive it returns false, true otherwise
+func (s *Subscription) Deactivate() bool {
+ return atomic.CompareAndSwapUint32(&s.active, 1, 0)
+}
+
+// Returns true if the active flag of the Subscription is set as 1
+// otherwise it returns false
+func (s *Subscription) IsActive() bool {
+ return atomic.LoadUint32(&s.active) == 1
+}
diff --git a/eventstream/eventstream_example_subscribe_test.go b/eventstream/eventstream_example_subscribe_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d74ba5a3b5bfa692e0756ac0445986a5630b723c
--- /dev/null
+++ b/eventstream/eventstream_example_subscribe_test.go
@@ -0,0 +1,30 @@
+package eventstream_test
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+)
+
+// Subscribe subscribes to events
+func ExampleEventStream_Subscribe() {
+ es := eventstream.NewEventStream()
+ handler := func(event interface{}) {
+ fmt.Println(event)
+ }
+
+ // only allow strings
+ predicate := func(event interface{}) bool {
+ _, ok := event.(string)
+ return ok
+ }
+
+ sub := es.SubscribeWithPredicate(handler, predicate)
+
+ es.Publish("Hello World")
+ es.Publish(1)
+
+ es.Unsubscribe(sub)
+
+ // Output: Hello World
+}
diff --git a/eventstream/eventstream_test.go b/eventstream/eventstream_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ee74fcb8ef70dc2ce86d6b5727267f87aef2b467
--- /dev/null
+++ b/eventstream/eventstream_test.go
@@ -0,0 +1,101 @@
+package eventstream_test
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEventStream_Subscribe(t *testing.T) {
+ es := &eventstream.EventStream{}
+ s := es.Subscribe(func(interface{}) {})
+ assert.NotNil(t, s)
+ assert.Equal(t, es.Length(), int32(1))
+}
+
+func TestEventStream_Unsubscribe(t *testing.T) {
+ es := &eventstream.EventStream{}
+ var c1, c2 int
+
+ s1 := es.Subscribe(func(interface{}) { c1++ })
+ s2 := es.Subscribe(func(interface{}) { c2++ })
+ assert.Equal(t, es.Length(), int32(2))
+
+ es.Unsubscribe(s2)
+ assert.Equal(t, es.Length(), int32(1))
+
+ es.Publish(1)
+ assert.Equal(t, 1, c1)
+
+ es.Unsubscribe(s1)
+ assert.Equal(t, es.Length(), int32(0))
+
+ es.Publish(1)
+ assert.Equal(t, 1, c1)
+ assert.Equal(t, 0, c2)
+}
+
+func TestEventStream_Publish(t *testing.T) {
+ es := &eventstream.EventStream{}
+
+ var v int
+ es.Subscribe(func(m interface{}) { v = m.(int) })
+
+ es.Publish(1)
+ assert.Equal(t, 1, v)
+
+ es.Publish(100)
+ assert.Equal(t, 100, v)
+}
+
+func TestEventStream_Subscribe_WithPredicate_IsCalled(t *testing.T) {
+ called := false
+ es := &eventstream.EventStream{}
+ es.SubscribeWithPredicate(
+ func(interface{}) { called = true },
+ func(m interface{}) bool { return true },
+ )
+ es.Publish("")
+
+ assert.True(t, called)
+}
+
+func TestEventStream_Subscribe_WithPredicate_IsNotCalled(t *testing.T) {
+ called := false
+ es := &eventstream.EventStream{}
+ es.SubscribeWithPredicate(
+ func(interface{}) { called = true },
+ func(m interface{}) bool { return false },
+ )
+ es.Publish("")
+
+ assert.False(t, called)
+}
+
+type Event struct {
+ i int
+}
+
+func BenchmarkEventStream(b *testing.B) {
+ es := eventstream.NewEventStream()
+ subs := make([]*eventstream.Subscription, 10)
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < 10; j++ {
+ sub := es.Subscribe(func(evt interface{}) {
+ if e := evt.(*Event); e.i != i {
+ b.Fatalf("expected i to be %d but its value is %d", i, e.i)
+ }
+ })
+ subs[j] = sub
+ }
+
+ es.Publish(&Event{i: i})
+ for j := range subs {
+ es.Unsubscribe(subs[j])
+ if subs[j].IsActive() {
+ b.Fatal("subscription should not be active")
+ }
+ }
+ }
+}
diff --git a/extensions/extensions.go b/extensions/extensions.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca3eb893f13414445cb5db7b95e6f5f9b5e99dea
--- /dev/null
+++ b/extensions/extensions.go
@@ -0,0 +1,38 @@
+package extensions
+
+import "sync/atomic"
+
+type ExtensionID int32
+
+var currentID int32 //nolint:gochecknoglobals
+
+type Extension interface {
+ ExtensionID() ExtensionID
+}
+
+type Extensions struct {
+ extensions []Extension
+}
+
+func NewExtensions() *Extensions {
+ ex := &Extensions{
+ extensions: make([]Extension, 100),
+ }
+
+ return ex
+}
+
+func NextExtensionID() ExtensionID {
+ id := atomic.AddInt32(¤tID, 1)
+
+ return ExtensionID(id)
+}
+
+func (ex *Extensions) Get(id ExtensionID) Extension {
+ return ex.extensions[id]
+}
+
+func (ex *Extensions) Register(extension Extension) {
+ id := extension.ExtensionID()
+ ex.extensions[id] = extension
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..1891f2fce45e5c7cbd674e75384ca0fa64827fce
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,117 @@
+module gitee.com/simplexyz/simpleactor-go
+
+go 1.21
+
+require (
+ github.com/Workiva/go-datastructures v1.1.1
+ github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2
+ github.com/couchbase/gocb v1.6.7
+ github.com/emirpasic/gods v1.18.1
+ github.com/gogo/protobuf v1.3.2
+ github.com/google/uuid v1.3.0
+ github.com/hashicorp/consul/api v1.18.0
+ github.com/opentracing/opentracing-go v1.2.0
+ github.com/orcaman/concurrent-map v1.0.0
+ github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b
+ github.com/stretchr/testify v1.8.4
+ go.opentelemetry.io/otel v1.16.0
+ go.opentelemetry.io/otel/exporters/prometheus v0.39.0
+ go.opentelemetry.io/otel/metric v1.16.0
+ go.opentelemetry.io/otel/sdk/metric v0.39.0
+ golang.org/x/net v0.17.0
+ google.golang.org/grpc v1.58.3
+ google.golang.org/protobuf v1.31.0
+)
+
+require (
+ github.com/go-zookeeper/zk v1.0.3
+ github.com/golang/mock v1.5.0
+ github.com/labstack/echo v3.3.10+incompatible
+ github.com/lithammer/shortuuid/v4 v4.0.0
+ github.com/twmb/murmur3 v1.1.6
+ go.etcd.io/etcd/client/v3 v3.5.7
+ golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
+ golang.org/x/sync v0.3.0
+ k8s.io/api v0.26.1
+ k8s.io/apimachinery v0.26.1
+ k8s.io/client-go v0.26.1
+)
+
+require google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
+
+require (
+ github.com/armon/go-metrics v0.4.0 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/coreos/go-semver v0.3.0 // indirect
+ github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.9.0 // indirect
+ github.com/fatih/color v1.13.0 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/swag v0.21.1 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/gnostic v0.6.9 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-hclog v1.2.0 // indirect
+ github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+ github.com/hashicorp/go-msgpack v0.5.5 // indirect
+ github.com/hashicorp/go-rootcerts v1.0.2 // indirect
+ github.com/hashicorp/golang-lru v0.5.4 // indirect
+ github.com/hashicorp/serf v0.10.1 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/labstack/gommon v0.3.1 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.16.0
+ github.com/prometheus/client_model v0.4.0 // indirect
+ github.com/prometheus/common v0.42.0 // indirect
+ github.com/prometheus/procfs v0.10.1 // indirect
+ github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/stretchr/objx v0.5.0 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasttemplate v1.2.1 // indirect
+ go.etcd.io/etcd/api/v3 v3.5.7 // indirect
+ go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
+ go.opentelemetry.io/otel/sdk v1.16.0 // indirect
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
+ go.uber.org/multierr v1.8.0 // indirect
+ go.uber.org/zap v1.21.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/oauth2 v0.10.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/term v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
+ gopkg.in/couchbase/gocbcore.v7 v7.1.18 // indirect
+ gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4 // indirect
+ gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 // indirect
+ gopkg.in/couchbaselabs/jsonx.v1 v1.0.1 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/klog/v2 v2.80.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
+ k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
+ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..e9480f8ee01b9832b64541f8c492f7b4965f42d3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,571 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
+github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q=
+github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2 h1:jEsFZ9d/ieJGVrx3fSPi8oe/qv21fRmyUL5cS3ZEn5A=
+github.com/asynkron/gofun v0.0.0-20220329210725-34fed760f4c2/go.mod h1:5GMOSqaYxNWwuVRWyampTPJEntwz7Mj9J8v1a7gSU2E=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/couchbase/gocb v1.6.7 h1:3sED4tqmzuKOQU2I/4u8ljrIXBe4lCzYhuD+/kPCyqs=
+github.com/couchbase/gocb v1.6.7/go.mod h1:AtRhXLpjgHmkRgG3e0K9t41qnWFonb8iohS/u/TZzxM=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
+github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g=
+github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4=
+github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU=
+github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
+github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
+github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
+github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
+github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
+github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
+github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
+github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
+github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
+github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
+github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
+github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
+github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
+github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
+github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
+github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
+github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
+github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
+github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
+github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
+github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b h1:h+3JX2VoWTFuyQEo87pStk/a99dzIO1mM9KxIyLPGTU=
+github.com/serialx/hashring v0.0.0-20200727003509-22c0c7ab6b1b/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
+go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
+go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
+go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
+go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
+go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA=
+go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y=
+go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
+go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
+go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
+go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
+go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
+go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI=
+go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
+go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
+golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
+golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
+golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
+google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0=
+google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
+google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/couchbase/gocbcore.v7 v7.1.18 h1:d4yfIXWdf/ZmyuJjwRVVlGT/yqx8ICy6fcT/ViaMZsI=
+gopkg.in/couchbase/gocbcore.v7 v7.1.18/go.mod h1:48d2Be0MxRtsyuvn+mWzqmoGUG9uA00ghopzOs148/E=
+gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4 h1:VVVoIV/nSw1w9ZnTEOjmkeJVcAzaCyxEujKglarxz7U=
+gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4/go.mod h1:ZjII0iKx4Veo6N6da+pEZu/ptNyKLg9QTVt7fFmR6sw=
+gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 h1:r5WoWGyeTJQiNGsoWAsMJfz0JFF14xc2TJrYSs09VXk=
+gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4/go.mod h1:jl/gd/aQ2S8whKVSTnsPs6n7BPeaAuw9UglBD/OF7eo=
+gopkg.in/couchbaselabs/jsonx.v1 v1.0.1 h1:giDAdTGcyXUuY+uFCWeJ2foukiqMTYl4ORSxCi/ybcc=
+gopkg.in/couchbaselabs/jsonx.v1 v1.0.1/go.mod h1:oR201IRovxvLW/eISevH12/+MiKHtNQAKfcX8iWZvJY=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
+k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
+k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
+k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
+k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
+k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
+k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/internal/core/debug.go b/internal/core/debug.go
new file mode 100644
index 0000000000000000000000000000000000000000..766068fff1627fba06ae046a4a8c8dc8c84441b7
--- /dev/null
+++ b/internal/core/debug.go
@@ -0,0 +1,35 @@
+package core
+
+import (
+ "fmt"
+ "runtime"
+ "strings"
+)
+
+func IdentifyPanic() string {
+ var name, file string
+ var line int
+ var pc [16]uintptr
+
+ n := runtime.Callers(3, pc[:])
+ for _, pc := range pc[:n] {
+ fn := runtime.FuncForPC(pc)
+ if fn == nil {
+ continue
+ }
+ file, line = fn.FileLine(pc)
+ name = fn.Name()
+ if !strings.HasPrefix(name, "runtime.") {
+ break
+ }
+ }
+
+ switch {
+ case name != "":
+ return fmt.Sprintf("%v:%v", name, line)
+ case file != "":
+ return fmt.Sprintf("%v:%v", file, line)
+ }
+
+ return fmt.Sprintf("pc:%x", pc)
+}
diff --git a/internal/queue/goring/LICENSE b/internal/queue/goring/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..c0ee81299bd368b4c38a7947d1651a1e32348313
--- /dev/null
+++ b/internal/queue/goring/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/internal/queue/goring/queue.go b/internal/queue/goring/queue.go
new file mode 100644
index 0000000000000000000000000000000000000000..d5b5db7c13f655d9c36559af0a266e75a21dc088
--- /dev/null
+++ b/internal/queue/goring/queue.go
@@ -0,0 +1,110 @@
+package goring
+
+import (
+ "sync"
+ "sync/atomic"
+)
+
+type ringBuffer struct {
+ buffer []interface{}
+ head int64
+ tail int64
+ mod int64
+}
+
+type Queue struct {
+ len int64
+ content *ringBuffer
+ lock sync.Mutex
+}
+
+func New(initialSize int64) *Queue {
+ return &Queue{
+ content: &ringBuffer{
+ buffer: make([]interface{}, initialSize),
+ head: 0,
+ tail: 0,
+ mod: initialSize,
+ },
+ len: 0,
+ }
+}
+
+func (q *Queue) Push(item interface{}) {
+ q.lock.Lock()
+ c := q.content
+ c.tail = (c.tail + 1) % c.mod
+ if c.tail == c.head {
+ var fillFactor int64 = 2
+ // we need to resize
+
+ newLen := c.mod * fillFactor
+ newBuff := make([]interface{}, newLen)
+
+ for i := int64(0); i < c.mod; i++ {
+ buffIndex := (c.tail + i) % c.mod
+ newBuff[i] = c.buffer[buffIndex]
+ }
+ // set the new buffer and reset head and tail
+ newContent := &ringBuffer{
+ buffer: newBuff,
+ head: 0,
+ tail: c.mod,
+ mod: newLen,
+ }
+ q.content = newContent
+ }
+ atomic.AddInt64(&q.len, 1)
+ q.content.buffer[q.content.tail] = item
+ q.lock.Unlock()
+}
+
+func (q *Queue) Length() int64 {
+ return atomic.LoadInt64(&q.len)
+}
+
+func (q *Queue) Empty() bool {
+ return q.Length() == 0
+}
+
+// single consumer
+func (q *Queue) Pop() (interface{}, bool) {
+ if q.Empty() {
+ return nil, false
+ }
+ // as we are a single consumer, no other thread can have poped the items there are guaranteed to be items now
+
+ q.lock.Lock()
+ c := q.content
+ c.head = (c.head + 1) % c.mod
+ res := c.buffer[c.head]
+ c.buffer[c.head] = nil
+ atomic.AddInt64(&q.len, -1)
+ q.lock.Unlock()
+ return res, true
+}
+
+func (q *Queue) PopMany(count int64) ([]interface{}, bool) {
+ if q.Empty() {
+ return nil, false
+ }
+
+ q.lock.Lock()
+ c := q.content
+
+ if count >= q.len {
+ count = q.len
+ }
+ atomic.AddInt64(&q.len, -count)
+
+ buffer := make([]interface{}, count)
+ for i := int64(0); i < count; i++ {
+ pos := (c.head + 1 + i) % c.mod
+ buffer[i] = c.buffer[pos]
+ c.buffer[pos] = nil
+ }
+ c.head = (c.head + count) % c.mod
+
+ q.lock.Unlock()
+ return buffer, true
+}
diff --git a/internal/queue/goring/queue_test.go b/internal/queue/goring/queue_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..797631789ec30c4cdeccfacad1fb46d083875ff6
--- /dev/null
+++ b/internal/queue/goring/queue_test.go
@@ -0,0 +1,112 @@
+package goring
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPushPop(t *testing.T) {
+ q := New(10)
+ q.Push("hello")
+ res, _ := q.Pop()
+ assert.Equal(t, "hello", res)
+ assert.True(t, q.Empty())
+}
+
+func TestPushPopRepeated(t *testing.T) {
+ q := New(10)
+ for i := 0; i < 100; i++ {
+ q.Push("hello")
+ res, _ := q.Pop()
+ assert.Equal(t, "hello", res)
+ assert.True(t, q.Empty())
+ }
+}
+
+func TestPushPopMany(t *testing.T) {
+ q := New(10)
+ for i := 0; i < 10000; i++ {
+ item := fmt.Sprintf("hello%v", i)
+ q.Push(item)
+ res, _ := q.Pop()
+ assert.Equal(t, item, res)
+ }
+ assert.True(t, q.Empty())
+}
+
+func TestPushPopMany2(t *testing.T) {
+ q := New(10)
+ for i := 0; i < 10000; i++ {
+ item := fmt.Sprintf("hello%v", i)
+ q.Push(item)
+ }
+ for i := 0; i < 10000; i++ {
+ item := fmt.Sprintf("hello%v", i)
+ res, _ := q.Pop()
+ assert.Equal(t, item, res)
+ }
+ assert.True(t, q.Empty())
+}
+
+//func TestLfQueueConsistency(t *testing.T) {
+// max := 1000000
+// c := 100
+// var wg sync.WaitGroup
+// wg.Add(1)
+// q := New(2)
+// go func() {
+// i := 0
+// seen := make(map[string]string)
+// for {
+// r, ok := q.Pop()
+// if !ok {
+// runtime.Gosched()
+//
+// continue
+// }
+// i++
+// if r == nil {
+// log.Printf("%#v, %#v", q, q.content)
+// panic("consistency failure")
+// }
+// s := r.(string)
+// _, present := seen[s]
+// if present {
+// log.Printf("item have already been seen %v", s)
+// t.FailNow()
+// }
+// seen[s] = s
+//
+// if i == max {
+// wg.Done()
+// return
+// }
+// }
+// }()
+//
+// for j := 0; j < c; j++ {
+// jj := j
+// cmax := max / c
+// go func() {
+// for i := 0; i < cmax; i++ {
+// if rand.Intn(10) == 0 {
+// time.Sleep(time.Duration(rand.Intn(1000)))
+// }
+// q.Push(fmt.Sprintf("%v %v", jj, i))
+// }
+// }()
+// }
+//
+// wg.Wait()
+// time.Sleep(500 * time.Millisecond)
+// // queue should be empty
+// for i := 0; i < 100; i++ {
+// r, ok := q.Pop()
+// if ok {
+// log.Printf("unexpected result %+v", r)
+// t.FailNow()
+// }
+// }
+//}
diff --git a/internal/queue/mpsc/mpsc.go b/internal/queue/mpsc/mpsc.go
new file mode 100644
index 0000000000000000000000000000000000000000..f23f7c3f5005ab7ad95a473ba1f8ba373b4cd5d8
--- /dev/null
+++ b/internal/queue/mpsc/mpsc.go
@@ -0,0 +1,66 @@
+// Package mpsc provides an efficient implementation of a multi-producer, single-consumer lock-free queue.
+//
+// The Push function is safe to call from multiple goroutines. The Pop and Empty APIs must only be
+// called from a single, consumer goroutine.
+package mpsc
+
+// This implementation is based on http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue
+
+import (
+ "sync/atomic"
+ "unsafe"
+)
+
+type node struct {
+ next *node
+ val interface{}
+}
+
+type Queue struct {
+ head, tail *node
+}
+
+func New() *Queue {
+ q := &Queue{}
+ stub := &node{}
+ q.head = stub
+ q.tail = stub
+ return q
+}
+
+// Push adds x to the back of the queue.
+//
+// Push can be safely called from multiple goroutines
+func (q *Queue) Push(x interface{}) {
+ n := new(node)
+ n.val = x
+ // current producer acquires head node
+ prev := (*node)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&q.head)), unsafe.Pointer(n)))
+
+ // release node to consumer
+ atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&prev.next)), unsafe.Pointer(n))
+}
+
+// Pop removes the item from the front of the queue or nil if the queue is empty
+//
+// Pop must be called from a single, consumer goroutine
+func (q *Queue) Pop() interface{} {
+ tail := q.tail
+ next := (*node)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tail.next)))) // acquire
+ if next != nil {
+ q.tail = next
+ v := next.val
+ next.val = nil
+ return v
+ }
+ return nil
+}
+
+// Empty returns true if the queue is empty
+//
+// Empty must be called from a single, consumer goroutine
+func (q *Queue) Empty() bool {
+ tail := q.tail
+ next := (*node)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tail.next))))
+ return next == nil
+}
diff --git a/internal/queue/mpsc/mpsc_test.go b/internal/queue/mpsc/mpsc_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..b11a9b2798585f747d6b3fcf8e42c304cff5f464
--- /dev/null
+++ b/internal/queue/mpsc/mpsc_test.go
@@ -0,0 +1,239 @@
+package mpsc
+
+import (
+ "fmt"
+ "runtime"
+ "sync"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestQueue_PushPop(t *testing.T) {
+ q := New()
+
+ q.Push(1)
+ q.Push(2)
+ assert.Equal(t, 1, q.Pop())
+ assert.Equal(t, 2, q.Pop())
+ assert.True(t, q.Empty())
+}
+
+func TestQueue_Empty(t *testing.T) {
+ q := New()
+ assert.True(t, q.Empty())
+ q.Push(1)
+ assert.False(t, q.Empty())
+}
+
+func TestQueue_PushPopOneProducer(t *testing.T) {
+ expCount := 100
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ q := New()
+ go func() {
+ i := 0
+ for {
+ r := q.Pop()
+ if r == nil {
+ runtime.Gosched()
+ continue
+ }
+ i++
+ if i == expCount {
+ wg.Done()
+ return
+ }
+ }
+ }()
+
+ var val interface{} = "foo"
+
+ for i := 0; i < expCount; i++ {
+ q.Push(val)
+ }
+
+ wg.Wait()
+}
+
+//func TestMpscQueueConsistency(t *testing.T) {
+// max := 1000000
+// c := runtime.NumCPU() / 2
+// cmax := max / c
+// var wg sync.WaitGroup
+// wg.Add(1)
+// q := New()
+//
+// go func() {
+// i := 0
+// seen := make(map[string]string)
+// for {
+// r := q.Pop()
+// if r == nil {
+// runtime.Gosched()
+//
+// continue
+// }
+// i++
+// s, _ := r.(string)
+// _, present := seen[s]
+// if present {
+// log.Printf("item have already been seen %v", s)
+// t.FailNow()
+// }
+// seen[s] = s
+// if i == cmax*c {
+// wg.Done()
+// return
+// }
+// }
+// }()
+//
+// for j := 0; j < c; j++ {
+// jj := j
+// go func() {
+// for i := 0; i < cmax; i++ {
+// if rand.Intn(10) == 0 {
+// time.Sleep(time.Duration(rand.Intn(1000)))
+// }
+// q.Push(fmt.Sprintf("%v %v", jj, i))
+// }
+// }()
+// }
+//
+// wg.Wait()
+// time.Sleep(500 * time.Millisecond)
+// // queue should be empty
+// for i := 0; i < 100; i++ {
+// r := q.Pop()
+// if r != nil {
+// log.Printf("unexpected result %+v", r)
+// t.FailNow()
+// }
+// }
+//}
+
+func benchmarkPushPop(count, c int) {
+ var wg sync.WaitGroup
+ wg.Add(1)
+ q := New()
+ go func() {
+ i := 0
+ for {
+ r := q.Pop()
+ if r == nil {
+ runtime.Gosched()
+ continue
+ }
+ i++
+ if i == count {
+ wg.Done()
+ return
+ }
+ }
+ }()
+
+ var val interface{} = "foo"
+
+ for i := 0; i < c; i++ {
+ go func(n int) {
+ for n > 0 {
+ q.Push(val)
+ n--
+ }
+ }(count / c)
+ }
+
+ wg.Wait()
+}
+
+func benchmarkChannelPushPop(count, c int) {
+ var wg sync.WaitGroup
+ wg.Add(1)
+ ch := make(chan interface{}, 100)
+ go func() {
+ i := 0
+ for {
+ <-ch
+ i++
+ if i == count {
+ wg.Done()
+ return
+ }
+ }
+ }()
+
+ var val interface{} = "foo"
+
+ for i := 0; i < c; i++ {
+ go func(n int) {
+ for n > 0 {
+ ch <- val
+ n--
+ }
+ }(count / c)
+ }
+}
+
+func BenchmarkPushPop(b *testing.B) {
+ benchmarks := []struct {
+ count int
+ concurrency int
+ }{
+ {
+ count: 10000,
+ concurrency: 1,
+ },
+ {
+ count: 10000,
+ concurrency: 2,
+ },
+ {
+ count: 10000,
+ concurrency: 4,
+ },
+ {
+ count: 10000,
+ concurrency: 8,
+ },
+ }
+ for _, bm := range benchmarks {
+ b.Run(fmt.Sprintf("%d_%d", bm.count, bm.concurrency), func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ benchmarkPushPop(bm.count, bm.concurrency)
+ }
+ })
+ }
+}
+
+func BenchmarkChannelPushPop(b *testing.B) {
+ benchmarks := []struct {
+ count int
+ concurrency int
+ }{
+ {
+ count: 10000,
+ concurrency: 1,
+ },
+ {
+ count: 10000,
+ concurrency: 2,
+ },
+ {
+ count: 10000,
+ concurrency: 4,
+ },
+ {
+ count: 10000,
+ concurrency: 8,
+ },
+ }
+ for _, bm := range benchmarks {
+ b.Run(fmt.Sprintf("%d_%d", bm.count, bm.concurrency), func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ benchmarkChannelPushPop(bm.count, bm.concurrency)
+ }
+ })
+ }
+}
diff --git a/log/caller.go b/log/caller.go
new file mode 100644
index 0000000000000000000000000000000000000000..6166aadd13e194e47cb00e369cbba7cbeac0203d
--- /dev/null
+++ b/log/caller.go
@@ -0,0 +1,34 @@
+package log
+
+import (
+ "runtime"
+ "strconv"
+ "strings"
+)
+
+type CallerInfo struct {
+ fname string
+ line int
+}
+
+func newCallerInfo(skip int) CallerInfo {
+ _, file, no, ok := runtime.Caller(skip)
+ if !ok {
+ return CallerInfo{"", 0}
+ }
+ return CallerInfo{fname: file, line: no}
+}
+
+func (ci *CallerInfo) ShortFileName() string {
+ fname := ci.fname
+ idx := strings.LastIndexByte(fname, '/')
+ if idx >= len(fname) {
+ } else {
+ fname = fname[idx+1:]
+ }
+ return fname
+}
+
+func (ci *CallerInfo) String() string {
+ return ci.ShortFileName() + ":" + strconv.Itoa(ci.line)
+}
diff --git a/log/encoder.go b/log/encoder.go
new file mode 100644
index 0000000000000000000000000000000000000000..1dcc9192870443584331f6fffd79744c4cbf4207
--- /dev/null
+++ b/log/encoder.go
@@ -0,0 +1,20 @@
+package log
+
+import (
+ "reflect"
+ "time"
+)
+
+type Encoder interface {
+ EncodeBool(key string, val bool)
+ EncodeFloat64(key string, val float64)
+ EncodeInt(key string, val int)
+ EncodeInt64(key string, val int64)
+ EncodeDuration(key string, val time.Duration)
+ EncodeUint(key string, val uint)
+ EncodeUint64(key string, val uint64)
+ EncodeString(key string, val string)
+ EncodeObject(key string, val interface{})
+ EncodeType(key string, val reflect.Type)
+ EncodeCaller(key string, val CallerInfo)
+}
diff --git a/log/event.go b/log/event.go
new file mode 100644
index 0000000000000000000000000000000000000000..d019b7cc4a2766ccd5466ce35e64e15da3eea856
--- /dev/null
+++ b/log/event.go
@@ -0,0 +1,13 @@
+package log
+
+import "time"
+
+type Event struct {
+ Time time.Time
+ Level Level
+ Prefix string
+ Caller CallerInfo
+ Message string
+ Context []Field
+ Fields []Field
+}
diff --git a/log/field.go b/log/field.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ca16a4b845182279710f976cd5c08248af18f66
--- /dev/null
+++ b/log/field.go
@@ -0,0 +1,221 @@
+package log
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+)
+
+type fieldType int
+
+const (
+ unknownType fieldType = iota
+ boolType
+ floatType
+ intType
+ int64Type
+ durationType
+ uintType
+ uint64Type
+ stringType
+ stringerType
+ errorType
+ objectType
+ typeOfType
+ skipType
+ callerType
+)
+
+type Field struct {
+ key string
+ fieldType fieldType
+ val int64
+ str string
+ obj interface{}
+}
+
+// Bool constructs a Field with the given key and value.
+func Bool(key string, val bool) Field {
+ var ival int64
+ if val {
+ ival = 1
+ }
+
+ return Field{key: key, fieldType: boolType, val: ival}
+}
+
+// Float64 constructs a Field with the given key and value.
+func Float64(key string, val float64) Field {
+ return Field{key: key, fieldType: floatType, val: int64(math.Float64bits(val))}
+}
+
+// Int constructs a Field with the given key and value. Marshaling ints is lazy.
+func Int(key string, val int) Field {
+ return Field{key: key, fieldType: intType, val: int64(val)}
+}
+
+// Int64 constructs a Field with the given key and value.
+func Int64(key string, val int64) Field {
+ return Field{key: key, fieldType: int64Type, val: val}
+}
+
+// Uint constructs a Field with the given key and value.
+func Uint(key string, val uint) Field {
+ return Field{key: key, fieldType: uintType, val: int64(val)}
+}
+
+// Uint64 constructs a Field with the given key and value.
+func Uint64(key string, val uint64) Field {
+ return Field{key: key, fieldType: uint64Type, val: int64(val)}
+}
+
+// String constructs a Field with the given key and value.
+func String(key string, val string) Field {
+ return Field{key: key, fieldType: stringType, str: val}
+}
+
+// PID constructs a Field with the given key and value.
+func PID(key string, val fmt.Stringer) Field {
+ if val == nil {
+ return Field{key: key, fieldType: objectType, obj: val}
+ }
+ return Field{key: key, fieldType: stringerType, obj: val}
+}
+
+// Stringer constructs a Field with the given key and the output of the value's
+// String method. The String is not evaluated until encoding.
+func Stringer(key string, val fmt.Stringer) Field {
+ if val == nil {
+ return Field{key: key, fieldType: objectType, obj: val}
+ }
+ return Field{key: key, fieldType: stringerType, obj: val}
+}
+
+// Time constructs a Field with the given key and value. It represents a
+// time.Time as a floating-point number of seconds since the Unix epoch.
+func Time(key string, val time.Time) Field {
+ return Float64(key, float64(val.UnixNano())/float64(time.Second))
+}
+
+// Error constructs a Field that lazily stores err.Error() under the key
+// "error". If passed a nil error, the field is skipped.
+func Error(err error) Field {
+ if err == nil {
+ return Field{fieldType: skipType}
+ }
+ return Field{key: "error", fieldType: errorType, obj: err}
+}
+
+// Stack constructs a Field that stores a stacktrace under the key "stacktrace".
+//
+// This is eager and therefore an expensive operation.
+func Stack() Field {
+ var name, file string
+ var line int
+ var pc [16]uintptr
+
+ n := runtime.Callers(4, pc[:])
+ callers := pc[:n]
+ frames := runtime.CallersFrames(callers)
+ for {
+ frame, more := frames.Next()
+ file = frame.File
+ line = frame.Line
+ name = frame.Function
+ if !strings.HasPrefix(name, "runtime.") || !more {
+ break
+ }
+ }
+
+ var str string
+ switch {
+ case name != "":
+ str = fmt.Sprintf("%v:%v", name, line)
+ case file != "":
+ str = fmt.Sprintf("%v:%v", file, line)
+ default:
+ str = fmt.Sprintf("pc:%x", pc)
+ }
+ return String("stacktrace", str)
+}
+
+// Duration constructs a Field with the given key and value.
+func Duration(key string, val time.Duration) Field {
+ return Field{key: key, fieldType: durationType, val: int64(val)}
+}
+
+// Object constructs a field with the given key and an arbitrary object.
+func Object(key string, val interface{}) Field {
+ return Field{key: key, fieldType: objectType, obj: val}
+}
+
+// TypeOf constructs a field with the given key and an arbitrary object that will log the type information lazily.
+func TypeOf(key string, val interface{}) Field {
+ return Field{key: key, fieldType: typeOfType, obj: val}
+}
+
+// Message constructs a field to store the message under the key message
+func Message(val interface{}) Field {
+ return Field{key: "message", fieldType: objectType, obj: val}
+}
+
+// CallerInfo is constructs with runtime.Caller
+
+// CallerSkip constructs a field with function name and number of line
+func CallerSkip(skip int) Field {
+ _, file, no, ok := runtime.Caller(skip)
+ if !ok {
+ return Field{key: "caller", fieldType: stringType, obj: "nil"}
+ }
+ return Field{
+ key: "caller",
+ fieldType: callerType,
+ obj: CallerInfo{
+ fname: file,
+ line: no,
+ },
+ }
+}
+
+func Caller() Field {
+ return CallerSkip(2)
+}
+
+// Encode encodes a field to a type safe val via the encoder.
+func (f Field) Encode(enc Encoder) {
+ switch f.fieldType {
+ case boolType:
+ enc.EncodeBool(f.key, f.val == 1)
+ case floatType:
+ enc.EncodeFloat64(f.key, math.Float64frombits(uint64(f.val)))
+ case intType:
+ enc.EncodeInt(f.key, int(f.val))
+ case int64Type:
+ enc.EncodeInt64(f.key, f.val)
+ case durationType:
+ enc.EncodeDuration(f.key, time.Duration(f.val))
+ case uintType:
+ enc.EncodeUint(f.key, uint(f.val))
+ case uint64Type:
+ enc.EncodeUint64(f.key, uint64(f.val))
+ case stringType:
+ enc.EncodeString(f.key, f.str)
+ case stringerType:
+ enc.EncodeString(f.key, f.obj.(fmt.Stringer).String())
+ case errorType:
+ enc.EncodeString(f.key, f.obj.(error).Error())
+ case objectType:
+ enc.EncodeObject(f.key, f.obj)
+ case typeOfType:
+ enc.EncodeType(f.key, reflect.TypeOf(f.obj))
+ case callerType:
+ enc.EncodeCaller(f.key, f.obj.(CallerInfo))
+ case skipType:
+ break
+ default:
+ panic(fmt.Sprintf("unknown field type found: %v", f))
+ }
+}
diff --git a/log/log.go b/log/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba94f7aba23421ed15d6d38c2ea30639c9c1f79a
--- /dev/null
+++ b/log/log.go
@@ -0,0 +1,123 @@
+/*
+Package log provides simple log interfaces
+*/
+package log
+
+import (
+ "sync/atomic"
+ "time"
+)
+
+// Level of log.
+type Level int32
+
+const (
+ MinLevel = Level(iota)
+ DebugLevel
+ InfoLevel
+ WarnLevel
+ ErrorLevel
+ OffLevel
+ DefaultLevel
+)
+
+var levelNames = [OffLevel + 1]string{"- ", "DEBUG", "INFO ", "WARN", "ERROR", "- "}
+
+func (l Level) String() string {
+ return levelNames[int(l)]
+}
+
+type Logger struct {
+ level Level
+ prefix string
+ context []Field
+ enableCaller bool
+}
+
+// New a Logger
+func New(level Level, prefix string, context ...Field) *Logger {
+ opts := Current
+ if level == DefaultLevel {
+ level = opts.logLevel
+ }
+ return &Logger{
+ level: level,
+ prefix: prefix,
+ context: context,
+ enableCaller: opts.enableCaller,
+ }
+}
+
+func (l *Logger) WithCaller() *Logger {
+ l.enableCaller = true
+ return l
+}
+
+func (l *Logger) With(fields ...Field) *Logger {
+ var ctx []Field
+
+ ll := len(l.context) + len(fields)
+ if ll > 0 {
+ ctx = make([]Field, 0, ll)
+ if len(l.context) > 0 {
+ ctx = append(ctx, l.context...)
+ }
+
+ if len(fields) > 0 {
+ ctx = append(ctx, fields...)
+ }
+ }
+
+ return &Logger{
+ level: l.level,
+ prefix: l.prefix,
+ context: ctx,
+ }
+}
+
+func (l *Logger) Level() Level {
+ return Level(atomic.LoadInt32((*int32)(&l.level)))
+}
+
+func (l *Logger) SetLevel(level Level) {
+ atomic.StoreInt32((*int32)(&l.level), int32(level))
+}
+
+func (l *Logger) newEvent(msg string, level Level, fields ...Field) Event {
+ ev := Event{
+ Time: time.Now(),
+ Level: level,
+ Prefix: l.prefix,
+ Message: msg,
+ Context: l.context,
+ Fields: fields,
+ }
+ if l.enableCaller {
+ ev.Caller = newCallerInfo(3)
+ }
+ return ev
+}
+
+func (l *Logger) Debug(msg string, fields ...Field) {
+ if l.Level() <= DebugLevel {
+ es.Publish(l.newEvent(msg, DebugLevel, fields...))
+ }
+}
+
+func (l *Logger) Info(msg string, fields ...Field) {
+ if l.Level() <= InfoLevel {
+ es.Publish(l.newEvent(msg, InfoLevel, fields...))
+ }
+}
+
+func (l *Logger) Warn(msg string, fields ...Field) {
+ if l.Level() <= WarnLevel {
+ es.Publish(l.newEvent(msg, WarnLevel, fields...))
+ }
+}
+
+func (l *Logger) Error(msg string, fields ...Field) {
+ if l.Level() <= ErrorLevel {
+ es.Publish(l.newEvent(msg, ErrorLevel, fields...))
+ }
+}
diff --git a/log/log_test.go b/log/log_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..43ed1b9a35c24a2d9027ba25bd865b9a87dc172e
--- /dev/null
+++ b/log/log_test.go
@@ -0,0 +1,53 @@
+package log
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLogger_With(t *testing.T) {
+ base := New(DebugLevel, "", Field{key: "first"})
+ l := base.With(Field{key: "second"})
+
+ assert.Equal(t, []Field{{key: "first"}, {key: "second"}}, l.context)
+}
+
+func Benchmark_OffLevel_TwoFields(b *testing.B) {
+ l := New(MinLevel, "")
+ for i := 0; i < b.N; i++ {
+ l.Debug("foo", Int("bar", 32), Bool("fum", false))
+ }
+}
+
+func Benchmark_OffLevel_OnlyContext(b *testing.B) {
+ l := New(MinLevel, "", Int("bar", 32), Bool("fum", false))
+ for i := 0; i < b.N; i++ {
+ l.Debug("foo")
+ }
+}
+
+func Benchmark_DebugLevel_OnlyContext_OneSubscriber(b *testing.B) {
+ Unsubscribe(sub)
+ s1 := Subscribe(func(Event) {})
+
+ l := New(DebugLevel, "", Int("bar", 32), Bool("fum", false))
+ for i := 0; i < b.N; i++ {
+ l.Debug("foo")
+ }
+ Unsubscribe(s1)
+}
+
+func Benchmark_DebugLevel_OnlyContext_MultipleSubscribers(b *testing.B) {
+ Unsubscribe(sub)
+ s1 := Subscribe(func(Event) {})
+ s2 := Subscribe(func(Event) {})
+
+ l := New(DebugLevel, "", Int("bar", 32), Bool("fum", false))
+ for i := 0; i < b.N; i++ {
+ l.Debug("foo")
+ }
+
+ Unsubscribe(s1)
+ Unsubscribe(s2)
+}
diff --git a/log/options.go b/log/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..d9dcaceb18d753e9b0e103ffccfbd1278f7666ae
--- /dev/null
+++ b/log/options.go
@@ -0,0 +1,77 @@
+package log
+
+import (
+ "os"
+)
+
+var (
+ Development = &Options{
+ logLevel: DebugLevel,
+ enableCaller: true,
+ }
+
+ Production = &Options{
+ logLevel: InfoLevel,
+ enableCaller: false,
+ }
+
+ Current = Production
+)
+
+func init() {
+ env := os.Getenv("PROTO_ACTOR_ENV")
+ switch env {
+ case "dev":
+ Current = Development
+ case "prod":
+ Current = Production
+ default:
+ Current = Production
+ }
+}
+
+// Options for log.
+type Options struct {
+ logLevel Level
+ enableCaller bool
+}
+
+// Setup is used to configure the log system
+func (o *Options) With(opts ...option) *Options {
+ cloned := *o
+ for _, opt := range opts {
+ opt(&cloned)
+ }
+ return &cloned
+}
+
+type option func(*Options)
+
+// WithEventSubscriber option replaces the default Event subscriber with fn.
+//
+// Specifying nil will disable logging of events.
+func WithEventSubscriber(fn func(evt Event)) option {
+ return func(opts *Options) {
+ resetEventSubscriber(fn)
+ }
+}
+
+// WithCaller option will print the file name and line number.
+func WithCaller(enabled bool) option {
+ return func(opts *Options) {
+ opts.enableCaller = enabled
+ }
+}
+
+func WithDefaultLevel(level Level) option {
+ if level == DefaultLevel {
+ level = InfoLevel
+ }
+ return func(opts *Options) {
+ opts.logLevel = level
+ }
+}
+
+func SetOptions(opts ...option) {
+ Current = Current.With(opts...)
+}
diff --git a/log/stream.go b/log/stream.go
new file mode 100644
index 0000000000000000000000000000000000000000..9cc39aab6b6c7523c360b9febb472279f028df3d
--- /dev/null
+++ b/log/stream.go
@@ -0,0 +1,85 @@
+package log
+
+import "sync"
+
+var es = &eventStream{}
+
+func Subscribe(fn func(evt Event)) *Subscription {
+ return es.Subscribe(fn)
+}
+
+func Unsubscribe(sub *Subscription) {
+ es.Unsubscribe(sub)
+}
+
+type eventStream struct {
+ sync.RWMutex
+ subscriptions []*Subscription
+}
+
+func (es *eventStream) Subscribe(fn func(evt Event)) *Subscription {
+ es.Lock()
+ sub := &Subscription{
+ es: es,
+ i: len(es.subscriptions),
+ fn: fn,
+ }
+ es.subscriptions = append(es.subscriptions, sub)
+ es.Unlock()
+ return sub
+}
+
+func (es *eventStream) Unsubscribe(sub *Subscription) {
+ if sub.i == -1 {
+ return
+ }
+
+ es.Lock()
+ i := sub.i
+ l := len(es.subscriptions) - 1
+
+ es.subscriptions[i] = es.subscriptions[l]
+ es.subscriptions[i].i = i
+ es.subscriptions[l] = nil
+ es.subscriptions = es.subscriptions[:l]
+ sub.i = -1
+
+ // TODO(SGC): implement resizing
+ if len(es.subscriptions) == 0 {
+ es.subscriptions = nil
+ }
+
+ es.Unlock()
+}
+
+func (es *eventStream) Publish(evt Event) {
+ es.RLock()
+ defer es.RUnlock()
+
+ for _, s := range es.subscriptions {
+ if evt.Level >= s.l {
+ s.fn(evt)
+ }
+ }
+}
+
+// Subscription is returned from the Subscribe function.
+//
+// This value and can be passed to Unsubscribe when the observer is no longer interested in receiving messages
+type Subscription struct {
+ es *eventStream
+ i int
+ fn func(event Event)
+ l Level
+}
+
+// WithMinLevel filter messages below the provided level
+//
+// For example, setting ErrorLevel will only pass error messages. Setting MinLevel will
+// allow all messages, and is the default.
+func (s *Subscription) WithMinLevel(level Level) *Subscription {
+ s.es.Lock()
+ s.l = level
+ s.es.Unlock()
+ return s
+}
diff --git a/log/string_encoder.go b/log/string_encoder.go
new file mode 100644
index 0000000000000000000000000000000000000000..70d7d9c8afe00f08c0a07a4d56fd27f97ff7efba
--- /dev/null
+++ b/log/string_encoder.go
@@ -0,0 +1,204 @@
+package log
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type ioLogger struct {
+ c chan Event
+ out io.Writer
+ buf []byte
+}
+
+var (
+ noStdErrLogs bool
+ sub *Subscription
+)
+
+// Disables Proto.Actor standard error logs if there is one
+// or more additional log subscribers registered
+func SetNoStdErrLogs() {
+ if len(es.subscriptions) >= 2 {
+ noStdErrLogs = true
+ }
+}
+
+func init() {
+ l := &ioLogger{c: make(chan Event, 100), out: os.Stderr}
+ resetEventSubscriber(func(evt Event) {
+ l.c <- evt
+ })
+ go l.listenEvent()
+}
+
+func resetEventSubscriber(f func(evt Event)) {
+ if sub != nil {
+ Unsubscribe(sub)
+ sub = nil
+ }
+ sub = Subscribe(f)
+}
+
+func (l *ioLogger) listenEvent() {
+ for true {
+ if noStdErrLogs {
+ Unsubscribe(sub)
+ break
+ }
+
+ e := <-l.c
+ l.writeEvent(e)
+ }
+}
+
+// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
+func itoa(buf *bytes.Buffer, i int, wid int) {
+ // Assemble decimal in reverse order.
+ var b [20]byte
+ bp := len(b) - 1
+ for i >= 10 || wid > 1 {
+ wid--
+ q := i / 10
+ b[bp] = byte('0' + i - q*10)
+ bp--
+ i = q
+ }
+ // i < 10
+ b[bp] = byte('0' + i)
+ buf.Write(b[bp:])
+}
+
+func (l *ioLogger) formatHeader(buf *bytes.Buffer, prefix string, t time.Time, loglv Level) {
+ // Y/M/D
+ year, month, day := t.Date()
+ itoa(buf, year, 4)
+ buf.WriteByte('/')
+ itoa(buf, int(month), 2)
+ buf.WriteByte('/')
+ itoa(buf, day, 2)
+ buf.WriteByte(' ')
+
+ // H/M/S
+ hour, min, sec := t.Clock()
+ itoa(buf, hour, 2)
+ buf.WriteByte(':')
+ itoa(buf, min, 2)
+ buf.WriteByte(':')
+ itoa(buf, sec, 2)
+
+ // no microseconds
+ // *buf = append(*buf, '.')
+ // itoa(buf, t.Nanosecond()/1e3, 6)
+
+ // log level
+ buf.WriteByte(' ')
+ buf.WriteString(loglv.String())
+ buf.WriteByte(' ')
+
+ // prefix
+ if len(prefix) > 0 {
+ buf.WriteString(prefix)
+ }
+ buf.WriteByte('\t')
+}
+
+func (l *ioLogger) formatCaller(buf *bytes.Buffer, caller *CallerInfo) {
+ fname := caller.ShortFileName()
+ buf.WriteString(fname)
+ buf.WriteByte(':')
+ buf.WriteString(strconv.Itoa(caller.line))
+ if v := (32 - len(fname)); v > 16 {
+ buf.Write([]byte{'\t', '\t', '\t'})
+ } else if v > 8 {
+ buf.Write([]byte{'\t', '\t'})
+ } else {
+ buf.WriteByte('\t')
+ }
+}
+
+func (l *ioLogger) writeEvent(e Event) {
+ buf := bytes.Buffer{}
+ l.formatHeader(&buf, e.Prefix, e.Time, e.Level)
+ if e.Caller.line > 0 {
+ l.formatCaller(&buf, &e.Caller)
+ }
+ if len(e.Message) > 0 {
+ buf.WriteString(e.Message)
+ buf.WriteByte(' ')
+ }
+
+ wr := ioEncoder{&buf}
+ for _, f := range e.Context {
+ f.Encode(wr)
+ buf.WriteByte(' ')
+ }
+ for _, f := range e.Fields {
+ f.Encode(wr)
+ buf.WriteByte(' ')
+ }
+ buf.WriteByte('\n')
+ l.out.Write(buf.Bytes())
+ buf.Reset()
+}
+
+type ioEncoder struct {
+ io.Writer
+}
+
+func (e ioEncoder) EncodeBool(key string, val bool) {
+ fmt.Fprintf(e, "%s=%t", key, val)
+}
+
+func (e ioEncoder) EncodeFloat64(key string, val float64) {
+ fmt.Fprintf(e, "%s=%f", key, val)
+}
+
+func (e ioEncoder) EncodeInt(key string, val int) {
+ fmt.Fprintf(e, "%s=%d", key, val)
+}
+
+func (e ioEncoder) EncodeInt64(key string, val int64) {
+ fmt.Fprintf(e, "%s=%d", key, val)
+}
+
+func (e ioEncoder) EncodeDuration(key string, val time.Duration) {
+ fmt.Fprintf(e, "%s=%s", key, val)
+}
+
+func (e ioEncoder) EncodeUint(key string, val uint) {
+ fmt.Fprintf(e, "%s=%d", key, val)
+}
+
+func (e ioEncoder) EncodeUint64(key string, val uint64) {
+ fmt.Fprintf(e, "%s=%d", key, val)
+}
+
+func (e ioEncoder) EncodeString(key string, val string) {
+ fmt.Fprintf(e, "%s=%q", key, val)
+}
+
+func (e ioEncoder) EncodeObject(key string, val interface{}) {
+ fmt.Fprintf(e, "%s=%v", key, val)
+}
+
+func (e ioEncoder) EncodeType(key string, val reflect.Type) {
+ fmt.Fprintf(e, "%s=%v", key, val)
+}
+
+func (e ioEncoder) EncodeCaller(key string, val CallerInfo) {
+ fname := val.fname
+ idx := strings.LastIndexByte(fname, '/')
+ if idx >= len(fname) {
+ // fname = fname
+ } else {
+ fname = fname[idx+1:]
+ }
+ fmt.Fprintf(e, "%s=%s:%d", key, fname, val.line)
+}
diff --git a/make.bat b/make.bat
new file mode 100644
index 0000000000000000000000000000000000000000..1a66113401a1629b924ede1476faed0e57871354
--- /dev/null
+++ b/make.bat
@@ -0,0 +1,61 @@
+@setlocal enabledelayedexpansion
+@setlocal enableextensions
+
+::@set PACKAGE_PATH=gitee.com\simplexyz\simpleactor-go
+@set WORK_DIR=%~dp0
+::@set GOPATH=%WORK_DIR%
+::@set GOPROXY=https://goproxy.cn
+
+@IF "%1" == "" call :all & cd %WORK_DIR% & goto :exit
+
+@IF "%1" == "all" call :all & cd %WORK_DIR% & goto :exit
+
+@IF "%1" == "clean-log" call :clean-log & cd %WORK_DIR% & goto :exit
+
+@IF "%1" == "mod-tidy" call :mod-tidy & cd %WORK_DIR% & goto :EXIT
+
+@IF "%1" == "update-proto" call :update-proto & cd %WORK_DIR% & goto :exit
+
+@echo unsupported operate [%1]
+
+@goto :exit
+
+
+:all
+@echo make all begin
+@echo make all end
+@goto :exit
+
+
+:clean-log
+del /f /s /q /a *.log
+@goto exit
+
+
+:mod-tidy
+@echo [mod-tidy] begin
+@echo off
+@for /R %WORK_DIR% %%f in (*.mod) do (
+ @set "GO_MOD_FILE_DIR=%%~dpf"
+ @cd !GO_MOD_FILE_DIR!
+ echo go mod tidy in [!GO_MOD_FILE_DIR!]
+ go mod tidy
+)
+@echo on
+@echo [mod-tidy] end
+@goto :EXIT
+
+:update-proto
+@echo [update-proto] begin
+go get -u github.com/gogo/protobuf/proto
+go get -u github.com/gogo/protobuf/protoc-gen-gogo
+go get -u github.com/gogo/protobuf/gogoproto
+go get -u github.com/gogo/protobuf/protoc-gen-gofast
+go get -u google.golang.org/grpc
+go get -u github.com/gogo/protobuf/protoc-gen-gogofast
+go get -u github.com/gogo/protobuf/protoc-gen-gogofaster
+go get -u github.com/gogo/protobuf/protoc-gen-gogoslick
+@echo [update-proto] end
+@goto :exit
+
+:exit
diff --git a/metrics/actor_metrics.go b/metrics/actor_metrics.go
new file mode 100644
index 0000000000000000000000000000000000000000..663cc6182dbf96e66de5138c0c2c4c764d2b2027
--- /dev/null
+++ b/metrics/actor_metrics.go
@@ -0,0 +1,156 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package metrics
+
+import (
+ "fmt"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/metric"
+)
+
+const LibName string = "protoactor"
+
+type ActorMetrics struct {
+ // Mutual Exclusion Primitive to use with ActorMailboxLength
+ mu *sync.Mutex
+
+ // MetricsID
+ ID string
+
+ // Actors
+ ActorFailureCount metric.Int64Counter
+ ActorMailboxLength metric.Int64ObservableGauge
+ ActorMessageReceiveHistogram metric.Float64Histogram
+ ActorRestartedCount metric.Int64Counter
+ ActorSpawnCount metric.Int64Counter
+ ActorStoppedCount metric.Int64Counter
+
+ // Deadletters
+ DeadLetterCount metric.Int64Counter
+
+ // Futures
+ FuturesStartedCount metric.Int64Counter
+ FuturesCompletedCount metric.Int64Counter
+ FuturesTimedOutCount metric.Int64Counter
+
+ // Threadpool
+ ThreadPoolLatency metric.Int64Histogram
+}
+
+// NewActorMetrics creates a new ActorMetrics value and returns a pointer to it
+func NewActorMetrics() *ActorMetrics {
+ instruments := newInstruments()
+ return instruments
+}
+
+// newInstruments will create instruments using a meter from
+// the given provider p
+func newInstruments() *ActorMetrics {
+ meter := otel.Meter(LibName)
+ instruments := ActorMetrics{mu: &sync.Mutex{}}
+
+ var err error
+
+ if instruments.ActorFailureCount, err = meter.Int64Counter(
+ "protoactor_actor_failure_count",
+ metric.WithDescription("Number of actor failures"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ActorFailureCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.ActorMessageReceiveHistogram, err = meter.Float64Histogram(
+ "protoactor_actor_message_receive_duration_seconds",
+ metric.WithDescription("Actor's messages received duration in seconds"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ActorMessageReceiveHistogram instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.ActorRestartedCount, err = meter.Int64Counter(
+ "protoactor_actor_restarted_count",
+ metric.WithDescription("Number of actors restarts"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ActorRestartedCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.ActorStoppedCount, err = meter.Int64Counter(
+ "protoactor_actor_stopped_count",
+ metric.WithDescription("Number of actors stopped"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ActorStoppedCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.ActorSpawnCount, err = meter.Int64Counter(
+ "protoactor_actor_spawn_count",
+ metric.WithDescription("Number of actors spawn"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ActorSpawnCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.DeadLetterCount, err = meter.Int64Counter(
+ "protoactor_deadletter_count",
+ metric.WithDescription("Number of deadletters"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create DeadLetterCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.FuturesCompletedCount, err = meter.Int64Counter(
+ "protoactor_futures_completed_count",
+ metric.WithDescription("Number of futures completed"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create FuturesCompletedCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.FuturesStartedCount, err = meter.Int64Counter(
+ "protoactor_futures_started_count",
+ metric.WithDescription("Number of futures started"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create FuturesStartedCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.FuturesTimedOutCount, err = meter.Int64Counter(
+ "protoactor_futures_timed_out_count",
+ metric.WithDescription("Number of futures timed out"),
+ metric.WithUnit("1"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create FuturesTimedOutCount instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ if instruments.ThreadPoolLatency, err = meter.Int64Histogram(
+ "protoactor_thread_pool_latency_duration_seconds",
+ metric.WithDescription("History of latency in second"),
+ metric.WithUnit("ms"),
+ ); err != nil {
+ err = fmt.Errorf("failed to create ThreadPoolLatency instrument, %w", err)
+ plog.Error(err.Error(), log.Error(err))
+ }
+
+ return &instruments
+}
+
+// SetActorMailboxLengthGauge makes sure access to ActorMailboxLength is sequenced
+func (am *ActorMetrics) SetActorMailboxLengthGauge(gauge metric.Int64ObservableGauge) {
+ // lock our mutex
+ am.mu.Lock()
+ defer am.mu.Unlock()
+
+ am.ActorMailboxLength = gauge
+}
diff --git a/metrics/log.go b/metrics/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..1210329e557e7bcae91e1c8155e014de67311e3d
--- /dev/null
+++ b/metrics/log.go
@@ -0,0 +1,14 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package metrics
+
+import "gitee.com/simplexyz/simpleactor-go/log"
+
+var plog = log.New(log.DefaultLevel, "[METRICS]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/metrics/metrics.go b/metrics/metrics.go
new file mode 100644
index 0000000000000000000000000000000000000000..ddcfdfb20ac509b1d83012926e54891f7f9f489f
--- /dev/null
+++ b/metrics/metrics.go
@@ -0,0 +1,55 @@
+// Copyright (C) 2017 - 2022 Asynkron.se
+
+package metrics
+
+import (
+ "fmt"
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "go.opentelemetry.io/otel/metric"
+)
+
+const InternalActorMetrics string = "internal.actor.metrics"
+
+type ProtoMetrics struct {
+ mu sync.Mutex
+ actorMetrics *ActorMetrics
+ knownMetrics map[string]*ActorMetrics
+}
+
+func NewProtoMetrics(provider metric.MeterProvider) *ProtoMetrics {
+ protoMetrics := ProtoMetrics{
+ actorMetrics: NewActorMetrics(),
+ knownMetrics: make(map[string]*ActorMetrics),
+ }
+
+ protoMetrics.Register(InternalActorMetrics, protoMetrics.actorMetrics)
+ return &protoMetrics
+}
+
+func (pm *ProtoMetrics) Instruments() *ActorMetrics { return pm.actorMetrics }
+
+func (pm *ProtoMetrics) Register(key string, instance *ActorMetrics) {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, ok := pm.knownMetrics[key]; ok {
+ err := fmt.Errorf("could not register instance %#v of metrics, %s already registered", instance, key)
+ plog.Error(err.Error(), log.Error(err))
+ return
+ }
+
+ pm.knownMetrics[key] = instance
+}
+
+func (pm *ProtoMetrics) Get(key string) *ActorMetrics {
+ metrics, ok := pm.knownMetrics[key]
+ if !ok {
+ err := fmt.Errorf("unknown metrics for the given %s key", key)
+ plog.Error(err.Error(), log.Error(err))
+ return nil
+ }
+
+ return metrics
+}
diff --git a/persistence/in_memory_provider.go b/persistence/in_memory_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..9e178e8feb529712eeb1fe422eea00b1b5133f69
--- /dev/null
+++ b/persistence/in_memory_provider.go
@@ -0,0 +1,85 @@
+package persistence
+
+import (
+ "sync"
+
+ "google.golang.org/protobuf/proto"
+)
+
+type entry struct {
+ eventIndex int // the event index right after snapshot
+ snapshot proto.Message
+ events []proto.Message
+}
+
+type InMemoryProvider struct {
+ snapshotInterval int
+ mu sync.RWMutex
+ store map[string]*entry // actorName -> a persistence entry
+}
+
+func NewInMemoryProvider(snapshotInterval int) *InMemoryProvider {
+ return &InMemoryProvider{
+ snapshotInterval: snapshotInterval,
+ store: make(map[string]*entry),
+ }
+}
+
+// loadOrInit returns the existing entry for actorName if present.
+// Otherwise, it initializes and returns an empty entry.
+// The loaded result is true if the entry was loaded, false if initialized.
+func (provider *InMemoryProvider) loadOrInit(actorName string) (e *entry, loaded bool) {
+ provider.mu.RLock()
+ e, ok := provider.store[actorName]
+ provider.mu.RUnlock()
+
+ if !ok {
+ provider.mu.Lock()
+ e = &entry{}
+ provider.store[actorName] = e
+ provider.mu.Unlock()
+ }
+
+ return e, ok
+}
+
+func (provider *InMemoryProvider) Restart() {}
+
+func (provider *InMemoryProvider) GetSnapshotInterval() int {
+ return provider.snapshotInterval
+}
+
+func (provider *InMemoryProvider) GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) {
+ entry, loaded := provider.loadOrInit(actorName)
+ if !loaded || entry.snapshot == nil {
+ return nil, 0, false
+ }
+ return entry.snapshot, entry.eventIndex, true
+}
+
+func (provider *InMemoryProvider) PersistSnapshot(actorName string, eventIndex int, snapshot proto.Message) {
+ entry, _ := provider.loadOrInit(actorName)
+ entry.eventIndex = eventIndex
+ entry.snapshot = snapshot
+}
+
+func (provider *InMemoryProvider) DeleteSnapshots(actorName string, inclusiveToIndex int) {
+}
+
+func (provider *InMemoryProvider) GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{})) {
+ entry, _ := provider.loadOrInit(actorName)
+ if eventIndexEnd == 0 {
+ eventIndexEnd = len(entry.events)
+ }
+ for _, e := range entry.events[eventIndexStart:eventIndexEnd] {
+ callback(e)
+ }
+}
+
+func (provider *InMemoryProvider) PersistEvent(actorName string, eventIndex int, event proto.Message) {
+ entry, _ := provider.loadOrInit(actorName)
+ entry.events = append(entry.events, event)
+}
+
+func (provider *InMemoryProvider) DeleteEvents(actorName string, inclusiveToIndex int) {
+}
diff --git a/persistence/messages.go b/persistence/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..45d0f3c8989bf45ec30cb546a5dd07798b7596d8
--- /dev/null
+++ b/persistence/messages.go
@@ -0,0 +1,10 @@
+package persistence
+
+type (
+ Replay struct{}
+ ReplayComplete struct{}
+ OfferSnapshot struct {
+ Snapshot interface{}
+ }
+)
+type RequestSnapshot struct{}
diff --git a/persistence/persistence_provider.go b/persistence/persistence_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..da9a0b4fbfa8b050b599192df3e12f030fa78f51
--- /dev/null
+++ b/persistence/persistence_provider.go
@@ -0,0 +1,31 @@
+package persistence
+
+import (
+ "google.golang.org/protobuf/proto"
+)
+
+// Provider is the abstraction used for persistence
+type Provider interface {
+ GetState() ProviderState
+}
+
+// ProviderState is an object containing the implementation for the provider
+type ProviderState interface {
+ SnapshotStore
+ EventStore
+
+ Restart()
+ GetSnapshotInterval() int
+}
+
+type SnapshotStore interface {
+ GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool)
+ PersistSnapshot(actorName string, snapshotIndex int, snapshot proto.Message)
+ DeleteSnapshots(actorName string, inclusiveToIndex int)
+}
+
+type EventStore interface {
+ GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{}))
+ PersistEvent(actorName string, eventIndex int, event proto.Message)
+ DeleteEvents(actorName string, inclusiveToIndex int)
+}
diff --git a/persistence/plugin.go b/persistence/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..66b43174d1c9fbd98f7c06b6f96517ae810c467e
--- /dev/null
+++ b/persistence/plugin.go
@@ -0,0 +1,75 @@
+package persistence
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "google.golang.org/protobuf/proto"
+)
+
+type persistent interface {
+ init(provider Provider, context actor.Context)
+ PersistReceive(message proto.Message)
+ PersistSnapshot(snapshot proto.Message)
+ Recovering() bool
+ Name() string
+}
+
+type Mixin struct {
+ eventIndex int
+ providerState ProviderState
+ name string
+ receiver receiver
+ recovering bool
+}
+
+// enforces that Mixin implements persistent interface
+// (if they diverge, code breaks in other packages)
+var _ persistent = (*Mixin)(nil)
+
+func (mixin *Mixin) Recovering() bool {
+ return mixin.recovering
+}
+
+func (mixin *Mixin) Name() string {
+ return mixin.name
+}
+
+func (mixin *Mixin) PersistReceive(message proto.Message) {
+ mixin.providerState.PersistEvent(mixin.Name(), mixin.eventIndex, message)
+ if mixin.eventIndex%mixin.providerState.GetSnapshotInterval() == 0 {
+ mixin.receiver.Receive(&actor.MessageEnvelope{Message: &RequestSnapshot{}})
+ }
+ mixin.eventIndex++
+}
+
+func (mixin *Mixin) PersistSnapshot(snapshot proto.Message) {
+ mixin.providerState.PersistSnapshot(mixin.Name(), mixin.eventIndex, snapshot)
+}
+
+func (mixin *Mixin) init(provider Provider, context actor.Context) {
+ if mixin.providerState == nil {
+ mixin.providerState = provider.GetState()
+ }
+
+ receiver := context.(receiver)
+
+ mixin.name = context.Self().Id
+ mixin.eventIndex = 0
+ mixin.receiver = receiver
+ mixin.recovering = true
+
+ mixin.providerState.Restart()
+ if snapshot, eventIndex, ok := mixin.providerState.GetSnapshot(mixin.Name()); ok {
+ mixin.eventIndex = eventIndex
+ receiver.Receive(&actor.MessageEnvelope{Message: snapshot})
+ }
+ mixin.providerState.GetEvents(mixin.Name(), mixin.eventIndex, 0 /* 0 means max */, func(e interface{}) {
+ receiver.Receive(&actor.MessageEnvelope{Message: e})
+ mixin.eventIndex++
+ })
+ mixin.recovering = false
+ receiver.Receive(&actor.MessageEnvelope{Message: &ReplayComplete{}})
+}
+
+type receiver interface {
+ Receive(message *actor.MessageEnvelope)
+}
diff --git a/persistence/plugin_test.go b/persistence/plugin_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc07a73ebc7550accd93f10158e84111670cf785
--- /dev/null
+++ b/persistence/plugin_test.go
@@ -0,0 +1,178 @@
+package persistence
+
+import (
+ "fmt"
+ "sync"
+ "testing"
+
+ "google.golang.org/protobuf/proto"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+/*
+Use some common types from persistence example to setup
+test cases
+*/
+
+const ActorName = "demo.actor"
+
+var system = actor.NewActorSystem()
+
+type dataStore struct {
+ providerState ProviderState
+}
+
+// initData sets up a data store
+// it adds one event to set state for every sting passed in
+// set the last snapshot to given index of those events
+func initData(snapshotInterval, lastSnapshot int, states ...string) *dataStore {
+ // add all events
+ state := NewInMemoryProvider(snapshotInterval)
+ for i, s := range states {
+ state.PersistEvent(ActorName, i, newMessage(s))
+ }
+ // mark one as a snapshot
+ if lastSnapshot < len(states) {
+ snapshot := states[lastSnapshot]
+ state.PersistSnapshot(
+ ActorName, lastSnapshot, newSnapshot(snapshot),
+ )
+ }
+ return &dataStore{providerState: state}
+}
+
+func (p *dataStore) GetState() ProviderState {
+ return p.providerState
+}
+
+type protoMsg struct {
+ proto.Message
+ state string
+}
+
+func (p *protoMsg) Reset() {}
+func (p *protoMsg) String() string { return p.state }
+func (p *protoMsg) ProtoMessage() {}
+
+type (
+ Message struct{ protoMsg }
+ Snapshot struct{ protoMsg }
+ Query struct{ protoMsg }
+)
+
+func newMessage(state string) *Message {
+ return &Message{protoMsg: protoMsg{state: state}}
+}
+
+func newSnapshot(state string) *Snapshot {
+ return &Snapshot{protoMsg: protoMsg{state: state}}
+}
+
+type myActor struct {
+ Mixin
+ state string
+}
+
+var _ actor.Actor = (*myActor)(nil)
+
+func makeActor() actor.Actor {
+ return &myActor{}
+}
+
+var (
+ queryWg sync.WaitGroup
+ queryState string
+)
+
+func (a *myActor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *RequestSnapshot:
+ // PersistSnapshot when requested
+ a.PersistSnapshot(newSnapshot(a.state))
+ case *Snapshot:
+ // Restore from Snapshot
+ a.state = msg.state
+ case *Message:
+ // Persist all events received outside of recovery
+ if !a.Recovering() {
+ a.PersistReceive(msg)
+ }
+ // Set state to whatever message says
+ a.state = msg.state
+ case *Query:
+ // TODO: this is poorly writen...
+ // I have no idea how to synchronously block on the
+ // receipt of a message for test cases.
+ queryState = a.state
+ queryWg.Done()
+ }
+}
+
+/****** test code *******/
+
+func TestRecovery(t *testing.T) {
+ cases := []struct {
+ init *dataStore
+ msgs []string
+ afterMsgs string
+ }{
+ // replay with no state
+ 0: {initData(5, 0), nil, ""},
+
+ // replay directly on snapshot, no more messages
+ 1: {initData(8, 2, "a", "b", "c"), nil, "c"},
+
+ // replay with snapshot and events, add another event
+ 2: {initData(8, 1, "a", "b", "c"), []string{"d"}, "d"},
+
+ // replay state and add an event, which triggers snapshot
+ 3: {initData(4, 1, "a", "b", "c"), []string{"d"}, "d"},
+
+ // replay state and add an event, which triggers snapshot,
+ // and then another one
+ 4: {initData(4, 1, "a", "b", "c"), []string{"d", "e"}, "e"},
+ }
+
+ for i, tc := range cases {
+ t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
+ rootContext := system.Root
+ props := actor.PropsFromProducer(makeActor,
+ actor.WithReceiverMiddleware(Using(tc.init)))
+ pid, err := rootContext.SpawnNamed(props, ActorName)
+ require.NoError(t, err)
+
+ // send a bunch of messages
+ for _, msg := range tc.msgs {
+ rootContext.Send(pid, newMessage(msg))
+ }
+
+ // ugly way to block on a response....
+ // TODO: I need some help here
+ queryWg.Add(1)
+ rootContext.Send(pid, &Query{})
+ queryWg.Wait()
+ // check the state after all these messages
+ assert.Equal(t, tc.afterMsgs, queryState)
+
+ // wait for shutdown
+ _ = rootContext.PoisonFuture(pid).Wait()
+
+ pid, err = rootContext.SpawnNamed(props, ActorName)
+ require.NoError(t, err)
+
+ // ugly way to block on a response....
+ // TODO: I need some help here
+ queryWg.Add(1)
+ rootContext.Send(pid, &Query{})
+ queryWg.Wait()
+ // check the state after all these messages
+ assert.Equal(t, tc.afterMsgs, queryState)
+
+ // shutdown at end of test for cleanup
+ _ = rootContext.PoisonFuture(pid).Wait()
+ })
+ }
+}
diff --git a/persistence/protocb/config.go b/persistence/protocb/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..12183df56198699f1b4ccf850a01db217dedfba2
--- /dev/null
+++ b/persistence/protocb/config.go
@@ -0,0 +1,20 @@
+package protocb
+
+type couchbaseConfig struct {
+ async bool
+ snapshotInterval int
+}
+
+type CouchbaseOption func(*couchbaseConfig)
+
+func WithAsync() CouchbaseOption {
+ return func(config *couchbaseConfig) {
+ config.async = true
+ }
+}
+
+func WithSnapshot(interval int) CouchbaseOption {
+ return func(config *couchbaseConfig) {
+ config.snapshotInterval = interval
+ }
+}
diff --git a/persistence/protocb/envelope.go b/persistence/protocb/envelope.go
new file mode 100644
index 0000000000000000000000000000000000000000..3836963aa324d795afd3a0e089277bef0f7c66db
--- /dev/null
+++ b/persistence/protocb/envelope.go
@@ -0,0 +1,46 @@
+package protocb
+
+import (
+ "encoding/json"
+ "log"
+
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/reflect/protoregistry"
+)
+
+type envelope struct {
+ Type string `json:"type"` // reflected message type so we can deserialize back
+ Message json.RawMessage `json:"event"` // this is still protobuf but the json form
+ EventIndex int `json:"eventIndex"` // event index in the event stream
+ DocType string `json:"doctype"` // type snapshot or event
+}
+
+func newEnvelope(message proto.Message, doctype string, eventIndex int) *envelope {
+ typeName := proto.MessageName(message)
+ bytes, err := json.Marshal(message)
+ if err != nil {
+ log.Fatal(err)
+ }
+ envelope := &envelope{
+ Type: string(typeName),
+ Message: bytes,
+ EventIndex: eventIndex,
+ DocType: doctype,
+ }
+ return envelope
+}
+
+func (envelope *envelope) message() proto.Message {
+ mt, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(envelope.Type))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ pm := mt.New().Interface()
+ err = json.Unmarshal(envelope.Message, pm)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return pm
+}
diff --git a/persistence/protocb/json_transcoder.go b/persistence/protocb/json_transcoder.go
new file mode 100644
index 0000000000000000000000000000000000000000..959eea4ea95d042f3c3d4a6f5d3c4eea8588d804
--- /dev/null
+++ b/persistence/protocb/json_transcoder.go
@@ -0,0 +1,21 @@
+package protocb
+
+import "encoding/json"
+
+type transcoder struct{}
+
+func (t transcoder) Decode(bytes []byte, flags uint32, out interface{}) error {
+ err := json.Unmarshal(bytes, &out)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (t transcoder) Encode(value interface{}) ([]byte, uint32, error) {
+ bytes, err := json.Marshal(value)
+ if err != nil {
+ return nil, 0, err
+ }
+ return bytes, 0, nil
+}
diff --git a/persistence/protocb/key.go b/persistence/protocb/key.go
new file mode 100644
index 0000000000000000000000000000000000000000..7672df9210b45c47ecb437d253373d910474c570
--- /dev/null
+++ b/persistence/protocb/key.go
@@ -0,0 +1,13 @@
+package protocb
+
+import "fmt"
+
+func formatEventKey(actorName string, eventIndex int) string {
+ key := fmt.Sprintf("%v-event-%010d", actorName, eventIndex)
+ return key
+}
+
+func formatSnapshotKey(actorName string, eventIndex int) string {
+ key := fmt.Sprintf("%v-snapshot-%010d", actorName, eventIndex)
+ return key
+}
diff --git a/persistence/protocb/provider.go b/persistence/protocb/provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..bcd3a950a4c97f314641147825d494d2504a1355
--- /dev/null
+++ b/persistence/protocb/provider.go
@@ -0,0 +1,55 @@
+package protocb
+
+import (
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/persistence"
+ "github.com/couchbase/gocb"
+)
+
+type Provider struct {
+ async bool
+ bucket *gocb.Bucket
+ bucketName string
+ snapshotInterval int
+ writer *actor.PID
+}
+
+func (provider *Provider) GetState() persistence.ProviderState {
+ return &cbState{
+ Provider: provider,
+ }
+}
+
+func New(actorSystem *actor.ActorSystem, bucketName string, baseU string, options ...CouchbaseOption) *Provider {
+ c, err := gocb.Connect(baseU)
+ if err != nil {
+ log.Fatalf("Error connecting: %v", err)
+ }
+ bucket, err := c.OpenBucketWithMt(bucketName, "")
+ if err != nil {
+ log.Fatalf("Error getting bucket: %v", err)
+ }
+ bucket.SetTranscoder(transcoder{})
+
+ config := &couchbaseConfig{}
+ for _, option := range options {
+ option(config)
+ }
+
+ provider := &Provider{
+ snapshotInterval: config.snapshotInterval,
+ async: config.async,
+ bucket: bucket,
+ bucketName: bucketName,
+ }
+
+ if config.async {
+ pid := actorSystem.Root.Spawn(actor.PropsFromFunc(newWriter(time.Second / 10000)))
+ provider.writer = pid
+ }
+
+ return provider
+}
diff --git a/persistence/protocb/provider_state.go b/persistence/protocb/provider_state.go
new file mode 100644
index 0000000000000000000000000000000000000000..6eb1b8dd9a5e5f6a955403c4c9aa61db701a48e5
--- /dev/null
+++ b/persistence/protocb/provider_state.go
@@ -0,0 +1,122 @@
+package protocb
+
+import (
+ "log"
+ "sync"
+
+ "github.com/couchbase/gocb"
+ "google.golang.org/protobuf/proto"
+)
+
+type cbState struct {
+ *Provider
+ wg sync.WaitGroup
+}
+
+func (state *cbState) Restart() {
+ // wait for any pending writes to complete
+ state.wg.Wait()
+}
+
+func (state *cbState) GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(event interface{})) {
+ q := gocb.NewN1qlQuery("SELECT b.* FROM `" + state.bucketName + "` b WHERE meta(b).id >= $1 and meta(b).id <= $2")
+ q.Consistency(gocb.RequestPlus)
+
+ // read all
+ if eventIndexEnd == 0 {
+ eventIndexEnd = 9999999999
+ }
+
+ var p []interface{}
+ p = append(p, formatEventKey(actorName, eventIndexStart))
+ p = append(p, formatEventKey(actorName, eventIndexEnd))
+
+ rows, err := state.bucket.ExecuteN1qlQuery(q, p)
+ if err != nil {
+ log.Fatalf("Error executing N1ql: %v", err)
+ }
+ defer func() {
+ err := rows.Close()
+ if err != nil {
+ log.Fatalf("Error closing gocb reader: %v", err)
+ }
+ }()
+
+ var row envelope
+ i := eventIndexStart
+ for rows.Next(&row) {
+ e := row.message()
+ if row.EventIndex != i {
+ log.Printf("%v, Invalid actor state, missing event %v", actorName, i)
+ return
+ }
+ callback(e)
+ i++
+ }
+}
+
+func (state *cbState) GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) {
+ q := gocb.NewN1qlQuery("SELECT b.* FROM `" + state.bucketName + "` b WHERE meta(b).id >= $1 and meta(b).id <= $2 order by b.eventIndex desc limit 1")
+ q.Consistency(gocb.RequestPlus)
+
+ var p []interface{}
+ p = append(p, formatSnapshotKey(actorName, 0))
+ p = append(p, formatSnapshotKey(actorName, 9999999999))
+
+ rows, err := state.bucket.ExecuteN1qlQuery(q, p)
+ if err != nil {
+ log.Fatalf("Error executing N1ql: %v", err)
+ }
+ defer func() {
+ err := rows.Close()
+ if err != nil {
+ log.Fatalf("Error closing gocb reader: %v", err)
+ }
+ }()
+
+ var row envelope
+ if rows.Next(&row) {
+ return row.message(), row.EventIndex, true
+ }
+ return nil, 0, false
+}
+
+func (provider *Provider) GetSnapshotInterval() int {
+ return provider.snapshotInterval
+}
+
+func (state *cbState) PersistEvent(actorName string, eventIndex int, event proto.Message) {
+ key := formatEventKey(actorName, eventIndex)
+ envelope := newEnvelope(event, "event", eventIndex)
+ state.persistEnvelope(key, envelope)
+}
+
+func (state *cbState) DeleteEvents(actorName string, inclusiveToIndex int) {
+ panic("implement me")
+}
+
+func (state *cbState) PersistSnapshot(actorName string, eventIndex int, snapshot proto.Message) {
+ key := formatSnapshotKey(actorName, eventIndex)
+ envelope := newEnvelope(snapshot, "snapshot", eventIndex)
+ state.persistEnvelope(key, envelope)
+}
+
+func (state *cbState) DeleteSnapshots(actorName string, inclusiveToIndex int) {
+ panic("implement me")
+}
+
+func (state *cbState) persistEnvelope(key string, envelope *envelope) {
+ state.wg.Add(1)
+ persist := func() {
+ _, err := state.bucket.Insert(key, envelope, 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ state.wg.Done()
+ }
+ if state.async {
+ // state.writer.Tell(&write{fun: persist})
+ } else {
+ persist()
+ }
+}
diff --git a/persistence/protocb/writer.go b/persistence/protocb/writer.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b021efe503bf617c9516ccb4e749f12144fbcaf
--- /dev/null
+++ b/persistence/protocb/writer.go
@@ -0,0 +1,21 @@
+package protocb
+
+import (
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type write struct {
+ fun func()
+}
+
+func newWriter(rate time.Duration) func(actor.Context) {
+ return func(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *write:
+ go msg.fun()
+ // time.Sleep(rate)
+ }
+ }
+}
diff --git a/persistence/receiver.go b/persistence/receiver.go
new file mode 100644
index 0000000000000000000000000000000000000000..a26d9b8315d96c1511aca439778bbc68053ee4d2
--- /dev/null
+++ b/persistence/receiver.go
@@ -0,0 +1,33 @@
+package persistence
+
+import (
+ "log"
+ "reflect"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+func Using(provider Provider) func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ return func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ fn := func(ctx actor.ReceiverContext, env *actor.MessageEnvelope) {
+ switch env.Message.(type) {
+
+ // intercept the started event, handle it and then apply the persistence init logic
+ case *actor.Started:
+ next(ctx, env)
+
+ // check if the actor is persistent
+ if p, ok := ctx.Actor().(persistent); ok {
+ // initialize it
+ p.init(provider, ctx.(actor.Context))
+ } else {
+ // not an persistent actor, bail out
+ log.Fatalf("Actor type %v is not persistent", reflect.TypeOf(ctx.Actor()))
+ }
+ default:
+ next(ctx, env)
+ }
+ }
+ return fn
+ }
+}
diff --git a/plugin/passivation.go b/plugin/passivation.go
new file mode 100644
index 0000000000000000000000000000000000000000..71340add2b697303c66d3229bc0df74dd3cd8436
--- /dev/null
+++ b/plugin/passivation.go
@@ -0,0 +1,69 @@
+package plugin
+
+import (
+ "log"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type PassivationAware interface {
+ Init(*actor.ActorSystem, *actor.PID, time.Duration)
+ Reset(time.Duration)
+ Cancel()
+}
+
+type PassivationHolder struct {
+ timer *time.Timer
+ done int32
+}
+
+func (state *PassivationHolder) Reset(duration time.Duration) {
+ if state.timer == nil {
+ log.Fatalf("Cannot reset passivation of a non-started actor")
+ }
+ if atomic.LoadInt32(&state.done) == 0 {
+ state.timer.Reset(duration)
+ }
+}
+
+func (state *PassivationHolder) Init(actorSystem *actor.ActorSystem, pid *actor.PID, duration time.Duration) {
+ state.timer = time.NewTimer(duration)
+ state.done = 0
+ go func() {
+ select {
+ case <-state.timer.C:
+ actorSystem.Root.Stop(pid)
+ atomic.StoreInt32(&state.done, 1)
+ break
+ }
+ }()
+}
+
+func (state *PassivationHolder) Cancel() {
+ if state.timer != nil {
+ state.timer.Stop()
+ }
+}
+
+type PassivationPlugin struct {
+ Duration time.Duration
+}
+
+func (pp *PassivationPlugin) OnStart(ctx actor.ReceiverContext) {
+ if a, ok := ctx.Actor().(PassivationAware); ok {
+ a.Init(ctx.ActorSystem(), ctx.Self(), pp.Duration)
+ }
+}
+
+func (pp *PassivationPlugin) OnOtherMessage(ctx actor.ReceiverContext, env *actor.MessageEnvelope) {
+ if p, ok := ctx.Actor().(PassivationAware); ok {
+ switch env.Message.(type) {
+ case *actor.Stopped:
+ p.Cancel()
+ default:
+ p.Reset(pp.Duration)
+ }
+ }
+}
diff --git a/plugin/passivation_test.go b/plugin/passivation_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2ce4d4497ec892c8ea2fd39640dd8723f872ddf
--- /dev/null
+++ b/plugin/passivation_test.go
@@ -0,0 +1,54 @@
+package plugin
+
+import (
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+var system = actor.NewActorSystem()
+
+type SmartActor struct {
+ PassivationHolder
+}
+
+func (state *SmartActor) Receive(context actor.Context) {
+ switch context.Message().(type) {
+ }
+}
+
+func TestPassivation(t *testing.T) {
+ if testing.Short() {
+ t.SkipNow()
+ }
+
+ UnitOfTime := 200 * time.Millisecond
+ PassivationDuration := 3 * UnitOfTime
+ rootContext := system.Root
+ props := actor.
+ PropsFromProducer(func() actor.Actor { return &SmartActor{} },
+ actor.WithReceiverMiddleware(Use(&PassivationPlugin{Duration: PassivationDuration})))
+
+ pid := rootContext.Spawn(props)
+ time.Sleep(UnitOfTime)
+ time.Sleep(UnitOfTime)
+ {
+ _, found := system.ProcessRegistry.GetLocal(pid.Id)
+ assert.True(t, found)
+ }
+ rootContext.Send(pid, "keepalive")
+ time.Sleep(UnitOfTime)
+ time.Sleep(UnitOfTime)
+ {
+ _, found := system.ProcessRegistry.GetLocal(pid.Id)
+ assert.True(t, found)
+ }
+ time.Sleep(UnitOfTime)
+ time.Sleep(UnitOfTime)
+ {
+ _, found := system.ProcessRegistry.GetLocal(pid.Id)
+ assert.False(t, found)
+ }
+}
diff --git a/plugin/plugin.go b/plugin/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb581c3d1b33bc7fc693932a58d72179a0ebcc48
--- /dev/null
+++ b/plugin/plugin.go
@@ -0,0 +1,27 @@
+package plugin
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type plugin interface {
+ OnStart(actor.ReceiverContext)
+ OnOtherMessage(actor.ReceiverContext, *actor.MessageEnvelope)
+}
+
+func Use(plugin plugin) func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ return func(next actor.ReceiverFunc) actor.ReceiverFunc {
+ fn := func(context actor.ReceiverContext, env *actor.MessageEnvelope) {
+ switch env.Message.(type) {
+ case *actor.Started:
+ plugin.OnStart(context)
+ default:
+ plugin.OnOtherMessage(context, env)
+ }
+
+ next(context, env)
+ }
+
+ return fn
+ }
+}
diff --git a/proto/actor.proto b/proto/actor.proto
new file mode 100644
index 0000000000000000000000000000000000000000..f2ec3477ead2cacd0f5059cc1d9dcc7741170663
--- /dev/null
+++ b/proto/actor.proto
@@ -0,0 +1,47 @@
+syntax = "proto3";
+package actor;
+option go_package = "gitee.com/simplexyz/simpleactor-go/actor";
+
+message PID {
+ string Address = 1;
+ string ID = 2;
+ uint32 RequestID = 3;
+}
+
+//user messages
+message PoisonPill {
+}
+
+message DeadLetterResponse {
+ PID Target = 1;
+}
+
+//system messages
+message Watch {
+ PID Watcher = 1;
+}
+
+message Unwatch {
+ PID Watcher = 1;
+}
+
+message Terminated {
+ PID Who = 1;
+ TerminatedReason Why = 2;
+}
+
+enum TerminatedReason {
+ Stopped = 0;
+ AddressTerminated = 1;
+ NotFound = 2;
+}
+
+message Stop {
+}
+
+message Touch {
+}
+
+message Touched {
+ PID Who = 1;
+}
\ No newline at end of file
diff --git a/proto/gen_actor.bat b/proto/gen_actor.bat
new file mode 100644
index 0000000000000000000000000000000000000000..d4bdfdbd539f24071ddbe8d066b3520eb5a94a20
--- /dev/null
+++ b/proto/gen_actor.bat
@@ -0,0 +1 @@
+protoc --go_out=..\actor --go_opt=paths=source_relative --proto_path=. actor.proto
\ No newline at end of file
diff --git a/protobuf/protoc-gen-gograinv2/Makefile b/protobuf/protoc-gen-gograinv2/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..523ee9e7866a32c3c04473a7a37e650ceced927d
--- /dev/null
+++ b/protobuf/protoc-gen-gograinv2/Makefile
@@ -0,0 +1,4 @@
+install:
+ go build .
+ @echo installing to ~/go/bin/protoc-gen-gograinv2
+ mv ./protoc-gen-gograinv2 ~/go/bin/
diff --git a/protobuf/protoc-gen-gograinv2/main.go b/protobuf/protoc-gen-gograinv2/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..81a4cc193b2cdb59566ff8078c488f41ae2558f6
--- /dev/null
+++ b/protobuf/protoc-gen-gograinv2/main.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "bytes"
+ "strings"
+ "text/template"
+
+ google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
+ plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin"
+ "github.com/gogo/protobuf/vanity/command"
+)
+
+func main() {
+ req := command.Read()
+ resp := generateCode(req, "_protoactor.go", true)
+ command.Write(resp)
+}
+
+func removePackagePrefix(name string, pname string) string {
+ return strings.Replace(name, "."+pname+".", "", 1)
+}
+
+func generateCode(req *plugin.CodeGeneratorRequest, filenameSuffix string, goFmt bool) *plugin.CodeGeneratorResponse {
+ response := &plugin.CodeGeneratorResponse{}
+ for _, f := range req.GetProtoFile() {
+ if !inStringSlice(f.GetName(), req.FileToGenerate) {
+ continue
+ }
+
+ s := generate(f)
+
+ // we only generate grains for proto files containing valid service definition
+ if len(f.GetService()) > 0 {
+ fileName := strings.Replace(f.GetName(), ".", "_", 1) + "actor.go"
+ r := &plugin.CodeGeneratorResponse_File{
+ Content: &s,
+ Name: &fileName,
+ }
+
+ response.File = append(response.File, r)
+ }
+ }
+
+ return response
+}
+
+func inStringSlice(val string, ss []string) bool {
+ for _, s := range ss {
+ if val == s {
+ return true
+ }
+ }
+ return false
+}
+
+func generate(file *google_protobuf.FileDescriptorProto) string {
+ pkg := ProtoAst(file)
+
+ t := template.New("grain")
+ t, _ = t.Parse(code)
+
+ var doc bytes.Buffer
+ t.Execute(&doc, pkg)
+ s := doc.String()
+
+ return s
+}
diff --git a/protobuf/protoc-gen-gograinv2/proto.go b/protobuf/protoc-gen-gograinv2/proto.go
new file mode 100644
index 0000000000000000000000000000000000000000..5342c0e3af71218bedcafcc34a8d923ac3ce207f
--- /dev/null
+++ b/protobuf/protoc-gen-gograinv2/proto.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "bytes"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ gogo "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
+)
+
+// code lifted from gogo proto
+var isGoKeyword = map[string]bool{
+ "break": true,
+ "case": true,
+ "chan": true,
+ "const": true,
+ "continue": true,
+ "default": true,
+ "else": true,
+ "defer": true,
+ "fallthrough": true,
+ "for": true,
+ "func": true,
+ "go": true,
+ "goto": true,
+ "if": true,
+ "import": true,
+ "interface": true,
+ "map": true,
+ "package": true,
+ "range": true,
+ "return": true,
+ "select": true,
+ "struct": true,
+ "switch": true,
+ "type": true,
+ "var": true,
+}
+
+// ProtoFile reprpesents a parsed proto file
+type ProtoFile struct {
+ PackageName string
+ Namespace string
+ Messages []*ProtoMessage
+ Services []*ProtoService
+}
+
+// ProtoMessage represents a parsed message in a proto file
+type ProtoMessage struct {
+ Name string
+ PascalName string
+}
+
+// ProtoService represents a parsed service in a proto file
+type ProtoService struct {
+ Name string
+ PascalName string
+ Methods []*ProtoMethod
+}
+
+// ProtoMethod represents a parsed method in a proto service
+type ProtoMethod struct {
+ Index int
+ Name string
+ PascalName string
+ Input *ProtoMessage
+ Output *ProtoMessage
+ InputStream bool
+ OutputStream bool
+}
+
+// ProtoAst transforms a FileDescriptor to an AST that can be used for code generation
+func ProtoAst(file *gogo.FileDescriptorProto) *ProtoFile {
+ pkg := &ProtoFile{}
+ pkg.Namespace = file.GetOptions().GetCsharpNamespace()
+
+ // let us check the option go_package is defined in the file and use that one instead of the
+ // default one
+ var packageName string
+ if file.GetOptions().GetGoPackage() != "" {
+ packageName = cleanPackageName(file.GetOptions().GetGoPackage())
+ } else {
+ packageName = cleanPackageName(file.GetPackage())
+ }
+
+ // let us the go package name
+ pkg.PackageName = packageName
+
+ messages := make(map[string]*ProtoMessage)
+ for _, message := range file.GetMessageType() {
+ m := &ProtoMessage{}
+ m.Name = message.GetName()
+ m.PascalName = MakeFirstLowerCase(m.Name)
+ pkg.Messages = append(pkg.Messages, m)
+ messages[m.Name] = m
+ }
+
+ for _, service := range file.GetService() {
+ s := &ProtoService{}
+ s.Name = service.GetName()
+ s.PascalName = MakeFirstLowerCase(s.Name)
+ pkg.Services = append(pkg.Services, s)
+
+ for i, method := range service.GetMethod() {
+ m := &ProtoMethod{}
+ m.Index = i
+ m.Name = method.GetName()
+ m.PascalName = MakeFirstLowerCase(m.Name)
+ // m.InputStream = *method.ClientStreaming
+ // m.OutputStream = *method.ServerStreaming
+ input := removePackagePrefix(method.GetInputType(), file.GetPackage())
+ output := removePackagePrefix(method.GetOutputType(), file.GetPackage())
+ m.Input = messages[input]
+ m.Output = messages[output]
+ s.Methods = append(s.Methods, m)
+ }
+ }
+ return pkg
+}
+
+func goPkgLastElement(full string) string {
+ pkgSplitted := strings.Split(full, "/")
+ return pkgSplitted[len(pkgSplitted)-1]
+}
+
+// MakeFirstLowerCase makes the first character in a string lower case
+func MakeFirstLowerCase(s string) string {
+ if len(s) < 2 {
+ return strings.ToLower(s)
+ }
+
+ bts := []byte(s)
+
+ lc := bytes.ToLower([]byte{bts[0]})
+ rest := bts[1:]
+
+ return string(bytes.Join([][]byte{lc, rest}, nil))
+}
+
+// cleanPackageName lifted from gogo generator
+// https://github.com/gogo/protobuf/blob/master/protoc-gen-gogo/generator/generator.go#L695
+func cleanPackageName(name string) string {
+ parts := strings.Split(name, "/")
+ name = parts[len(parts)-1]
+
+ name = strings.Map(badToUnderscore, name)
+ // Identifier must not be keyword: insert _.
+ if isGoKeyword[name] {
+ name = "_" + name
+ }
+ // Identifier must not begin with digit: insert _.
+ if r, _ := utf8.DecodeRuneInString(name); unicode.IsDigit(r) {
+ name = "_" + name
+ }
+ return name
+}
+
+// badToUnderscore lifted from gogo generator
+func badToUnderscore(r rune) rune {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
+ return r
+ }
+ return '_'
+}
diff --git a/protobuf/protoc-gen-gograinv2/template.go b/protobuf/protoc-gen-gograinv2/template.go
new file mode 100644
index 0000000000000000000000000000000000000000..c7191d084e9a99e30dd315ad2921053f00bc3ff9
--- /dev/null
+++ b/protobuf/protoc-gen-gograinv2/template.go
@@ -0,0 +1,177 @@
+package main
+
+const code = `{{ if .Services -}}
+// Package {{.PackageName}} is generated by protoactor-go/protoc-gen-gograin@0.1.0
+package {{.PackageName}}
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/cluster"
+ logmod "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/protobuf/proto"
+)
+
+var (
+ plog = logmod.New(logmod.InfoLevel, "[GRAIN][{{.PackageName}}]")
+ _ = proto.Marshal
+ _ = fmt.Errorf
+ _ = math.Inf
+)
+
+// SetLogLevel sets the log level.
+func SetLogLevel(level logmod.Level) {
+ plog.SetLevel(level)
+}
+
+{{ range $service := .Services -}}
+var x{{ $service.Name }}Factory func() {{ $service.Name }}
+
+// {{ $service.Name }}Factory produces a {{ $service.Name }}
+func {{ $service.Name }}Factory(factory func() {{ $service.Name }}) {
+ x{{ $service.Name }}Factory = factory
+}
+
+// Get{{ $service.Name }}GrainClient instantiates a new {{ $service.Name }}GrainClient with given Identity
+func Get{{ $service.Name }}GrainClient(c *cluster.Cluster, id string) *{{ $service.Name }}GrainClient {
+ if c == nil {
+ panic(fmt.Errorf("nil cluster instance"))
+ }
+ if id == "" {
+ panic(fmt.Errorf("empty id"))
+ }
+ return &{{ $service.Name }}GrainClient{Identity: id, cluster: c}
+}
+
+// Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }}
+func Get{{ $service.Name }}Kind(opts ...actor.PropsOption) *cluster.Kind {
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &{{ $service.Name }}Actor{
+ Timeout: 60 * time.Second,
+ }
+ }, opts...)
+ kind := cluster.NewKind("{{ $service.Name }}", props)
+ return kind
+}
+
+// Get{{ $service.Name }}Kind instantiates a new cluster.Kind for {{ $service.Name }}
+func New{{ $service.Name }}Kind(factory func() {{ $service.Name }}, timeout time.Duration ,opts ...actor.PropsOption) *cluster.Kind {
+ x{{ $service.Name }}Factory = factory
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return &{{ $service.Name }}Actor{
+ Timeout: timeout,
+ }
+ }, opts...)
+ kind := cluster.NewKind("{{ $service.Name }}", props)
+ return kind
+}
+
+// {{ $service.Name }} interfaces the services available to the {{ $service.Name }}
+type {{ $service.Name }} interface {
+ Init(ctx cluster.GrainContext)
+ Terminate(ctx cluster.GrainContext)
+ ReceiveDefault(ctx cluster.GrainContext)
+ {{ range $method := $service.Methods -}}
+ {{ $method.Name }}(*{{ $method.Input.Name }}, cluster.GrainContext) (*{{ $method.Output.Name }}, error)
+ {{ end }}
+}
+
+// {{ $service.Name }}GrainClient holds the base data for the {{ $service.Name }}Grain
+type {{ $service.Name }}GrainClient struct {
+ Identity string
+ cluster *cluster.Cluster
+}
+{{ range $method := $service.Methods}}
+// {{ $method.Name }} requests the execution on to the cluster with CallOptions
+func (g *{{ $service.Name }}GrainClient) {{ $method.Name }}(r *{{ $method.Input.Name }}, opts ...cluster.GrainCallOption) (*{{ $method.Output.Name }}, error) {
+ bytes, err := proto.Marshal(r)
+ if err != nil {
+ return nil, err
+ }
+ reqMsg := &cluster.GrainRequest{MethodIndex: {{ $method.Index }}, MessageData: bytes}
+ resp, err := g.cluster.Call(g.Identity, "{{ $service.Name }}", reqMsg, opts...)
+ if err != nil {
+ return nil, err
+ }
+ switch msg := resp.(type) {
+ case *cluster.GrainResponse:
+ result := &{{ $method.Output.Name }}{}
+ err = proto.Unmarshal(msg.MessageData, result)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ case *cluster.GrainErrorResponse:
+ return nil, errors.New(msg.Err)
+ default:
+ return nil, errors.New("unknown response")
+ }
+}
+{{ end }}
+
+// {{ $service.Name }}Actor represents the actor structure
+type {{ $service.Name }}Actor struct {
+ ctx cluster.GrainContext
+ inner {{ $service.Name }}
+ Timeout time.Duration
+}
+
+// Receive ensures the lifecycle of the actor for the received message
+func (a *{{ $service.Name }}Actor) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started: //pass
+ case *cluster.ClusterInit:
+ a.ctx = cluster.NewGrainContext(ctx, msg.Identity, msg.Cluster)
+ a.inner = x{{ $service.Name }}Factory()
+ a.inner.Init(a.ctx)
+
+ if a.Timeout > 0 {
+ ctx.SetReceiveTimeout(a.Timeout)
+ }
+ case *actor.ReceiveTimeout:
+ ctx.Poison(ctx.Self())
+ case *actor.Stopped:
+ a.inner.Terminate(a.ctx)
+ case actor.AutoReceiveMessage: // pass
+ case actor.SystemMessage: // pass
+
+ case *cluster.GrainRequest:
+ switch msg.MethodIndex {
+ {{ range $method := $service.Methods -}}
+ case {{ $method.Index }}:
+ req := &{{ $method.Input.Name }}{}
+ err := proto.Unmarshal(msg.MessageData, req)
+ if err != nil {
+ plog.Error("{{ $method.Name }}({{ $method.Input.Name }}) proto.Unmarshal failed.", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ r0, err := a.inner.{{ $method.Name }}(req, a.ctx)
+ if err != nil {
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ bytes, err := proto.Marshal(r0)
+ if err != nil {
+ plog.Error("{{ $method.Name }}({{ $method.Input.Name }}) proto.Marshal failed", logmod.Error(err))
+ resp := &cluster.GrainErrorResponse{Err: err.Error()}
+ ctx.Respond(resp)
+ return
+ }
+ resp := &cluster.GrainResponse{MessageData: bytes}
+ ctx.Respond(resp)
+ {{ end }}
+ }
+ default:
+ a.inner.ReceiveDefault(a.ctx)
+ }
+}
+{{ end -}}
+{{ end -}}
+`
diff --git a/remote/activator_actor.go b/remote/activator_actor.go
new file mode 100644
index 0000000000000000000000000000000000000000..19581c0d385b0fdfdc75d55b99bf4ffac5ad56d1
--- /dev/null
+++ b/remote/activator_actor.go
@@ -0,0 +1,143 @@
+package remote
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+// Register a known actor props by name
+func (r *Remote) Register(kind string, props *actor.Props) {
+ r.kinds[kind] = props
+}
+
+// GetKnownKinds returns a slice of known actor "Kinds"
+func (r *Remote) GetKnownKinds() []string {
+ keys := make([]string, 0, len(r.kinds))
+ for k := range r.kinds {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+type activator struct {
+ remote *Remote
+}
+
+// ErrActivatorUnavailable : this error will not panic the Activator.
+// It simply tells Partition this Activator is not available
+// Partition will then find next available Activator to spawn
+var ErrActivatorUnavailable = &ActivatorError{ResponseStatusCodeUNAVAILABLE.ToInt32(), true}
+
+type ActivatorError struct {
+ Code int32
+ DoNotPanic bool
+}
+
+func (e *ActivatorError) Error() string {
+ return fmt.Sprint(e.Code)
+}
+
+// ActivatorForAddress returns a PID for the activator at the given address
+func (r *Remote) ActivatorForAddress(address string) *actor.PID {
+ pid := actor.NewPID(address, "activator")
+ return pid
+}
+
+// SpawnFuture spawns a remote actor and returns a Future that completes once the actor is started
+func (r *Remote) SpawnFuture(address, name, kind string, timeout time.Duration) *actor.Future {
+ activator := r.ActivatorForAddress(address)
+ f := r.actorSystem.Root.RequestFuture(activator, &ActorPidRequest{
+ Name: name,
+ Kind: kind,
+ }, timeout)
+ return f
+}
+
+// Spawn spawns a remote actor of a given type at a given address
+func (r *Remote) Spawn(address, kind string, timeout time.Duration) (*ActorPidResponse, error) {
+ return r.SpawnNamed(address, "", kind, timeout)
+}
+
+// SpawnNamed spawns a named remote actor of a given type at a given address
+func (r *Remote) SpawnNamed(address, name, kind string, timeout time.Duration) (*ActorPidResponse, error) {
+ res, err := r.SpawnFuture(address, name, kind, timeout).Result()
+ if err != nil {
+ return nil, err
+ }
+ switch msg := res.(type) {
+ case *ActorPidResponse:
+ return msg, nil
+ default:
+ return nil, errors.New("remote: Unknown response when remote activating")
+ }
+}
+
+func newActivatorActor(remote *Remote) actor.Producer {
+ return func() actor.Actor {
+ return &activator{
+ remote: remote,
+ }
+ }
+}
+
+func (a *activator) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *actor.Started:
+ plog.Info("Started Activator")
+ case *Ping:
+ context.Respond(&Pong{})
+ case *ActorPidRequest:
+ props, exist := a.remote.kinds[msg.Kind]
+
+ // if props not exist, return error and panic
+ if !exist {
+ response := &ActorPidResponse{
+ StatusCode: ResponseStatusCodeERROR.ToInt32(),
+ }
+ context.Respond(response)
+ panic(fmt.Errorf("no Props found for kind %s", msg.Kind))
+ }
+
+ name := msg.Name
+
+ // unnamed actor, assign auto ExtensionID
+ if name == "" {
+ name = context.ActorSystem().ProcessRegistry.NextId()
+ }
+
+ pid, err := context.SpawnNamed(props, "Remote$"+name)
+
+ if err == nil {
+ response := &ActorPidResponse{Pid: pid}
+ context.Respond(response)
+ } else if err == actor.ErrNameExists {
+ response := &ActorPidResponse{
+ Pid: pid,
+ StatusCode: ResponseStatusCodePROCESSNAMEALREADYEXIST.ToInt32(),
+ }
+ context.Respond(response)
+ } else if aErr, ok := err.(*ActivatorError); ok {
+ response := &ActorPidResponse{
+ StatusCode: aErr.Code,
+ }
+ context.Respond(response)
+ if !aErr.DoNotPanic {
+ panic(err)
+ }
+ } else {
+ response := &ActorPidResponse{
+ StatusCode: ResponseStatusCodeERROR.ToInt32(),
+ }
+ context.Respond(response)
+ panic(err)
+ }
+ case actor.SystemMessage, actor.AutoReceiveMessage:
+ // ignore
+ default:
+ plog.Error("Activator received unknown message", log.TypeOf("type", msg), log.Message(msg))
+ }
+}
diff --git a/remote/blocklist.go b/remote/blocklist.go
new file mode 100644
index 0000000000000000000000000000000000000000..326ac56b00cb3394073defc9bf6fb3e4ab8dc29a
--- /dev/null
+++ b/remote/blocklist.go
@@ -0,0 +1,52 @@
+/*
+ Copyright (C) 2017 - 2022 Asynkron.se
+*/
+
+package remote
+
+import (
+ "sync"
+
+ "github.com/asynkron/gofun/set"
+)
+
+// TODO: document it
+type BlockList struct {
+ mu *sync.RWMutex
+ blockedMembers *set.ImmutableSet[string]
+}
+
+func NewBlockList() *BlockList {
+ blocklist := BlockList{
+ mu: &sync.RWMutex{},
+ blockedMembers: set.NewImmutable[string](),
+ }
+ return &blocklist
+}
+
+func (bl *BlockList) BlockedMembers() set.Set[string] {
+ return bl.blockedMembers
+}
+
+// Block adds the given memberID list to the BlockList
+func (bl *BlockList) Block(memberIDs ...string) {
+ // acquire our mutual exclusion primitive
+ bl.mu.Lock()
+ defer bl.mu.Unlock()
+
+ bl.blockedMembers = bl.blockedMembers.AddRange(memberIDs...)
+}
+
+// IsBlocked returns true if the given memberID string has been
+// ever added to the BlockList
+func (bl *BlockList) IsBlocked(memberID string) bool {
+ // acquire our mutual exclusion primitive for reading
+ return bl.blockedMembers.Contains(memberID)
+}
+
+// Len returns the number of blocked members
+func (bl *BlockList) Len() int {
+ bl.mu.RLock()
+ defer bl.mu.RUnlock()
+ return bl.blockedMembers.Size()
+}
diff --git a/remote/build.sh b/remote/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..8fd39b18cd3343ac0eab0015508c761b511838a4
--- /dev/null
+++ b/remote/build.sh
@@ -0,0 +1,2 @@
+protoc -I=../actor --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative --proto_path=. remote.proto
+
diff --git a/remote/config-opts.go b/remote/config-opts.go
new file mode 100644
index 0000000000000000000000000000000000000000..124e4ccf3d7c9bb6bf2618d9edefe353cfefeac4
--- /dev/null
+++ b/remote/config-opts.go
@@ -0,0 +1,70 @@
+package remote
+
+import "google.golang.org/grpc"
+
+type ConfigOption func(config *Config)
+
+// WithEndpointWriterBatchSize sets the batch size for the endpoint writer
+func WithEndpointWriterBatchSize(batchSize int) ConfigOption {
+ return func(config *Config) {
+ config.EndpointWriterBatchSize = batchSize
+ }
+}
+
+// WithEndpointWriterQueueSize sets the queue size for the endpoint writer
+func WithEndpointWriterQueueSize(queueSize int) ConfigOption {
+ return func(config *Config) {
+ config.EndpointWriterQueueSize = queueSize
+ }
+}
+
+// WithEndpointManagerBatchSize sets the batch size for the endpoint manager
+func WithEndpointManagerBatchSize(batchSize int) ConfigOption {
+ return func(config *Config) {
+ config.EndpointManagerBatchSize = batchSize
+ }
+}
+
+// WithEndpointManagerQueueSize sets the queue size for the endpoint manager
+func WithEndpointManagerQueueSize(queueSize int) ConfigOption {
+ return func(config *Config) {
+ config.EndpointManagerQueueSize = queueSize
+ }
+}
+
+// WithDialOptions sets the dial options for the remote
+func WithDialOptions(options ...grpc.DialOption) ConfigOption {
+ return func(config *Config) {
+ config.DialOptions = options
+ }
+}
+
+// WithServerOptions sets the server options for the remote
+func WithServerOptions(options ...grpc.ServerOption) ConfigOption {
+ return func(config *Config) {
+ config.ServerOptions = options
+ }
+}
+
+// WithCallOptions sets the call options for the remote
+func WithCallOptions(options ...grpc.CallOption) ConfigOption {
+ return func(config *Config) {
+ config.CallOptions = options
+ }
+}
+
+// WithAdvertisedHost sets the advertised host for the remote
+func WithAdvertisedHost(address string) ConfigOption {
+ return func(config *Config) {
+ config.AdvertisedHost = address
+ }
+}
+
+// WithKinds adds the kinds to the remote
+func WithKinds(kinds ...*Kind) ConfigOption {
+ return func(config *Config) {
+ for _, k := range kinds {
+ config.Kinds[k.Kind] = k.Props
+ }
+ }
+}
diff --git a/remote/config.go b/remote/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..5dc4e109c16dda8517369d8c82d7bdb00790487d
--- /dev/null
+++ b/remote/config.go
@@ -0,0 +1,59 @@
+package remote
+
+import (
+ "fmt"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "google.golang.org/grpc"
+)
+
+func defaultConfig() *Config {
+ return &Config{
+ AdvertisedHost: "",
+ DialOptions: []grpc.DialOption{grpc.WithInsecure()},
+ EndpointWriterBatchSize: 1000,
+ EndpointManagerBatchSize: 1000,
+ EndpointWriterQueueSize: 1000000,
+ EndpointManagerQueueSize: 1000000,
+ Kinds: make(map[string]*actor.Props),
+ MaxRetryCount: 5,
+ }
+}
+
+func newConfig(options ...ConfigOption) *Config {
+ config := defaultConfig()
+ for _, option := range options {
+ option(config)
+ }
+ return config
+}
+
+// Address returns the address of the remote
+func (rc Config) Address() string {
+ return fmt.Sprintf("%v:%v", rc.Host, rc.Port)
+}
+
+// Configure configures the remote
+func Configure(host string, port int, options ...ConfigOption) *Config {
+ c := newConfig(options...)
+ c.Host = host
+ c.Port = port
+
+ return c
+}
+
+// Config is the configuration for the remote
+type Config struct {
+ Host string
+ Port int
+ AdvertisedHost string
+ ServerOptions []grpc.ServerOption
+ CallOptions []grpc.CallOption
+ DialOptions []grpc.DialOption
+ EndpointWriterBatchSize int
+ EndpointWriterQueueSize int
+ EndpointManagerBatchSize int
+ EndpointManagerQueueSize int
+ Kinds map[string]*actor.Props
+ MaxRetryCount int
+}
diff --git a/remote/doc.go b/remote/doc.go
new file mode 100644
index 0000000000000000000000000000000000000000..5744e869e97538e53aa85ce934adbaa7f7a96639
--- /dev/null
+++ b/remote/doc.go
@@ -0,0 +1,4 @@
+/*
+Package remote provides access to actors across a network or other I/O connection.
+*/
+package remote
diff --git a/remote/endpoint_manager.go b/remote/endpoint_manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..e087fd31dcaef9234860a220795a1ed284c25234
--- /dev/null
+++ b/remote/endpoint_manager.go
@@ -0,0 +1,298 @@
+package remote
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/eventstream"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+type endpointLazy struct {
+ // valueFunc func() *endpoint
+ unloaded uint32
+ once sync.Once
+ endpoint atomic.Value
+ manager *endpointManager
+ address string
+}
+
+func NewEndpointLazy(em *endpointManager, address string) *endpointLazy {
+ return &endpointLazy{
+ manager: em,
+ address: address,
+ }
+}
+
+func (el *endpointLazy) connect() {
+ em := el.manager
+ system := em.remote.actorSystem
+ rst, _ := system.Root.RequestFuture(em.endpointSupervisor, el.address, -1).Result()
+ ep := rst.(*endpoint)
+ el.Set(ep)
+}
+
+func (el *endpointLazy) Set(ep *endpoint) {
+ el.endpoint.Store(ep)
+}
+
+func (el *endpointLazy) Get() *endpoint {
+ el.once.Do(el.connect)
+ ep := el.endpoint.Load()
+ return ep.(*endpoint)
+}
+
+type endpoint struct {
+ writer *actor.PID
+ watcher *actor.PID
+}
+
+func (ep *endpoint) Address() string {
+ return ep.watcher.GetAddress()
+}
+
+type endpointManager struct {
+ connections *sync.Map
+ remote *Remote
+ endpointSub *eventstream.Subscription
+ endpointSupervisor *actor.PID
+ activator *actor.PID
+ stopped bool
+ endpointReaderConnections *sync.Map
+}
+
+func newEndpointManager(r *Remote) *endpointManager {
+ return &endpointManager{
+ connections: &sync.Map{},
+ remote: r,
+ stopped: false,
+ endpointReaderConnections: &sync.Map{},
+ }
+}
+
+func (em *endpointManager) start() {
+ eventStream := em.remote.actorSystem.EventStream
+ em.endpointSub = eventStream.
+ SubscribeWithPredicate(em.endpointEvent, func(m interface{}) bool {
+ switch m.(type) {
+ case *EndpointTerminatedEvent, *EndpointConnectedEvent:
+ return true
+ }
+ return false
+ })
+ em.startActivator()
+ em.startSupervisor()
+
+ if err := em.waiting(3 * time.Second); err != nil {
+ panic(err)
+ }
+ plog.Info("Started EndpointManager")
+}
+
+func (em *endpointManager) waiting(timeout time.Duration) error {
+ ctx := em.remote.actorSystem.Root
+ if _, err := ctx.RequestFuture(em.activator, &Ping{}, timeout).Result(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (em *endpointManager) stop() {
+ em.stopped = true
+ r := em.remote
+ r.actorSystem.EventStream.Unsubscribe(em.endpointSub)
+ if err := em.stopActivator(); err != nil {
+ plog.Error("stop endpoint activator failed", log.Error(err))
+ }
+ if err := em.stopSupervisor(); err != nil {
+ plog.Error("stop endpoint supervisor failed", log.Error(err))
+ }
+ em.endpointSub = nil
+ em.connections = nil
+ if em.endpointReaderConnections != nil {
+ em.endpointReaderConnections.Range(func(key interface{}, value interface{}) bool {
+ channel := value.(chan bool)
+ channel <- true
+ em.endpointReaderConnections.Delete(key)
+ return true
+ })
+ }
+ plog.Info("Stopped EndpointManager")
+}
+
+func (em *endpointManager) startActivator() {
+ p := newActivatorActor(em.remote)
+ props := actor.PropsFromProducer(p, actor.WithGuardian(actor.RestartingSupervisorStrategy()))
+ pid, err := em.remote.actorSystem.Root.SpawnNamed(props, "activator")
+ if err != nil {
+ panic(err)
+ }
+ em.activator = pid
+}
+
+func (em *endpointManager) stopActivator() error {
+ return em.remote.actorSystem.Root.StopFuture(em.activator).Wait()
+}
+
+func (em *endpointManager) startSupervisor() {
+ r := em.remote
+ props := actor.PropsFromProducer(func() actor.Actor {
+ return newEndpointSupervisor(r)
+ },
+ actor.WithGuardian(actor.RestartingSupervisorStrategy()),
+ actor.WithSupervisor(actor.RestartingSupervisorStrategy()),
+ actor.WithDispatcher(actor.NewSynchronizedDispatcher(300)))
+
+ pid, err := r.actorSystem.Root.SpawnNamed(props, "EndpointSupervisor")
+ if err != nil {
+ panic(err)
+ }
+ em.endpointSupervisor = pid
+}
+
+func (em *endpointManager) stopSupervisor() error {
+ r := em.remote
+ return r.actorSystem.Root.StopFuture(em.endpointSupervisor).Wait()
+}
+
+func (em *endpointManager) endpointEvent(evn interface{}) {
+ switch msg := evn.(type) {
+ case *EndpointTerminatedEvent:
+ plog.Debug("EndpointManager received endpoint terminated event, removing endpoint", log.Message(evn))
+ em.removeEndpoint(msg)
+ case *EndpointConnectedEvent:
+ endpoint := em.ensureConnected(msg.Address)
+ em.remote.actorSystem.Root.Send(endpoint.watcher, msg)
+ }
+}
+
+func (em *endpointManager) remoteTerminate(msg *remoteTerminate) {
+ if em.stopped {
+ return
+ }
+ address := msg.Watchee.Address
+ endpoint := em.ensureConnected(address)
+ em.remote.actorSystem.Root.Send(endpoint.watcher, msg)
+}
+
+func (em *endpointManager) remoteWatch(msg *remoteWatch) {
+ if em.stopped {
+ return
+ }
+ address := msg.Watchee.Address
+ endpoint := em.ensureConnected(address)
+ em.remote.actorSystem.Root.Send(endpoint.watcher, msg)
+}
+
+func (em *endpointManager) remoteUnwatch(msg *remoteUnwatch) {
+ if em.stopped {
+ return
+ }
+ address := msg.Watchee.Address
+ endpoint := em.ensureConnected(address)
+ em.remote.actorSystem.Root.Send(endpoint.watcher, msg)
+}
+
+func (em *endpointManager) remoteDeliver(msg *remoteDeliver) {
+ if em.stopped {
+ // send to deadletter
+ em.remote.actorSystem.EventStream.Publish(&actor.DeadLetterEvent{
+ PID: msg.target,
+ Message: msg.message,
+ Sender: msg.sender,
+ })
+ return
+ }
+ address := msg.target.Address
+ endpoint := em.ensureConnected(address)
+ em.remote.actorSystem.Root.Send(endpoint.writer, msg)
+}
+
+func (em *endpointManager) ensureConnected(address string) *endpoint {
+ e, ok := em.connections.Load(address)
+ if !ok {
+ el := NewEndpointLazy(em, address)
+ e, _ = em.connections.LoadOrStore(address, el)
+ }
+ el := e.(*endpointLazy)
+ return el.Get()
+}
+
+// func (em *endpointManager) ensureConnected(address string) *endpoint {
+// e, ok := em.connections.Load(address)
+// if !ok {
+// el := &endpointLazy{}
+// var once sync.Once
+// el.valueFunc = func() *endpoint {
+// once.Do(func() {
+// rst, _ := em.remote.actorSystem.Root.RequestFuture(em.endpointSupervisor, address, -1).Result()
+// ep := rst.(*endpoint)
+// el.valueFunc = func() *endpoint {
+// return ep
+// }
+// })
+// return el.valueFunc()
+// }
+// e, _ = em.connections.LoadOrStore(address, el)
+// }
+
+// el := e.(*endpointLazy)
+// return el.valueFunc()
+// }
+
+func (em *endpointManager) removeEndpoint(msg *EndpointTerminatedEvent) {
+ v, ok := em.connections.Load(msg.Address)
+ if ok {
+ le := v.(*endpointLazy)
+ if atomic.CompareAndSwapUint32(&le.unloaded, 0, 1) {
+ em.connections.Delete(msg.Address)
+ ep := le.Get()
+ plog.Debug("Sending EndpointTerminatedEvent to EndpointWatcher ans EndpointWriter", log.String("address", msg.Address))
+ em.remote.actorSystem.Root.Send(ep.watcher, msg)
+ em.remote.actorSystem.Root.Send(ep.writer, msg)
+ }
+ }
+}
+
+type endpointSupervisor struct {
+ remote *Remote
+}
+
+func newEndpointSupervisor(remote *Remote) actor.Actor {
+ return &endpointSupervisor{
+ remote: remote,
+ }
+}
+
+func (state *endpointSupervisor) Receive(ctx actor.Context) {
+ if address, ok := ctx.Message().(string); ok {
+ plog.Debug("EndpointSupervisor spawning EndpointWriter and EndpointWatcher", log.String("address", address))
+ e := &endpoint{
+ writer: state.spawnEndpointWriter(state.remote, address, ctx),
+ watcher: state.spawnEndpointWatcher(state.remote, address, ctx),
+ }
+ ctx.Respond(e)
+ }
+}
+
+func (state *endpointSupervisor) HandleFailure(actorSystem *actor.ActorSystem, supervisor actor.Supervisor, child *actor.PID, rs *actor.RestartStatistics, reason interface{}, message interface{}) {
+ plog.Debug("EndpointSupervisor handling failure", log.Object("reason", reason), log.Message(message))
+ supervisor.RestartChildren(child)
+}
+
+func (state *endpointSupervisor) spawnEndpointWriter(remote *Remote, address string, ctx actor.Context) *actor.PID {
+ props := actor.
+ PropsFromProducer(endpointWriterProducer(remote, address, remote.config),
+ actor.WithMailbox(endpointWriterMailboxProducer(remote.config.EndpointWriterBatchSize, remote.config.EndpointWriterQueueSize)))
+ pid := ctx.Spawn(props)
+ return pid
+}
+
+func (state *endpointSupervisor) spawnEndpointWatcher(remote *Remote, address string, ctx actor.Context) *actor.PID {
+ props := actor.
+ PropsFromProducer(newEndpointWatcher(remote, address))
+ pid := ctx.Spawn(props)
+ return pid
+}
diff --git a/remote/endpoint_reader.go b/remote/endpoint_reader.go
new file mode 100644
index 0000000000000000000000000000000000000000..a40ea4913d3120577d1de9707555da8f1e7b9f74
--- /dev/null
+++ b/remote/endpoint_reader.go
@@ -0,0 +1,253 @@
+package remote
+
+import (
+ "errors"
+ "io"
+
+ "google.golang.org/protobuf/proto"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "golang.org/x/net/context"
+)
+
+type endpointReader struct {
+ suspended bool
+ remote *Remote
+}
+
+func (s *endpointReader) mustEmbedUnimplementedRemotingServer() {
+ // TODO implement me
+ panic("implement me")
+}
+
+func (s *endpointReader) ListProcesses(ctx context.Context, request *ListProcessesRequest) (*ListProcessesResponse, error) {
+ panic("implement me")
+}
+
+func (s *endpointReader) GetProcessDiagnostics(ctx context.Context, request *GetProcessDiagnosticsRequest) (*GetProcessDiagnosticsResponse, error) {
+ panic("implement me")
+}
+
+func newEndpointReader(r *Remote) *endpointReader {
+ return &endpointReader{
+ remote: r,
+ }
+}
+
+func (s *endpointReader) Receive(stream Remoting_ReceiveServer) error {
+ disconnectChan := make(chan bool, 1)
+ s.remote.edpManager.endpointReaderConnections.Store(stream, disconnectChan)
+ defer func() {
+ close(disconnectChan)
+ }()
+
+ go func() {
+ // endpointManager sends true
+ // endpointReader sends false
+ if <-disconnectChan {
+ plog.Debug("EndpointReader is telling to remote that it's leaving")
+ err := stream.Send(&RemoteMessage{
+ MessageType: &RemoteMessage_DisconnectRequest{
+ DisconnectRequest: &DisconnectRequest{},
+ },
+ })
+ if err != nil {
+ plog.Error("EndpointReader failed to send disconnection message", log.Error(err))
+ }
+ } else {
+ s.remote.edpManager.endpointReaderConnections.Delete(stream)
+ plog.Debug("EndpointReader removed active endpoint from endpointManager")
+ }
+ }()
+
+ for {
+ msg, err := stream.Recv()
+ switch {
+ case errors.Is(err, io.EOF):
+ plog.Info("EndpointReader stream closed")
+ disconnectChan <- false
+ return nil
+ case err != nil:
+ plog.Info("EndpointReader failed to read", log.Error(err))
+ return err
+ case s.suspended:
+ continue
+ }
+
+ switch t := msg.MessageType.(type) {
+ case *RemoteMessage_ConnectRequest:
+ plog.Debug("EndpointReader received connect request", log.Message(t.ConnectRequest))
+ c := t.ConnectRequest
+ _, err := s.OnConnectRequest(stream, c)
+ if err != nil {
+ plog.Error("EndpointReader failed to handle connect request", log.Error(err))
+ return err
+ }
+ case *RemoteMessage_MessageBatch:
+ m := t.MessageBatch
+ err := s.onMessageBatch(m)
+ if err != nil {
+ return err
+ }
+ default:
+ {
+ plog.Warn("EndpointReader received unknown message type")
+ }
+ }
+ }
+}
+
+func (s *endpointReader) OnConnectRequest(stream Remoting_ReceiveServer, c *ConnectRequest) (bool, error) {
+ switch tt := c.ConnectionType.(type) {
+ case *ConnectRequest_ServerConnection:
+ {
+ sc := tt.ServerConnection
+ s.onServerConnection(stream, sc)
+ }
+ case *ConnectRequest_ClientConnection:
+ {
+ // TODO implement me
+ plog.Error("ClientConnection not implemented")
+ }
+ default:
+ plog.Error("EndpointReader received unknown connection type")
+ return true, nil
+ }
+ return false, nil
+}
+
+func (s *endpointReader) onMessageBatch(m *MessageBatch) error {
+ var (
+ sender *actor.PID
+ target *actor.PID
+ )
+
+ for _, envelope := range m.Envelopes {
+ data := envelope.MessageData
+
+ sender = deserializeSender(sender, envelope.Sender, envelope.SenderRequestId, m.Senders)
+ target = deserializeTarget(target, envelope.Target, envelope.TargetRequestId, m.Targets)
+ if target == nil {
+ plog.Error("EndpointReader received message with unknown target", log.Int("target", int(envelope.Target)), log.Int("targetRequestId", int(envelope.TargetRequestId)))
+ return errors.New("unknown target")
+ }
+
+ message, err := Deserialize(data, m.TypeNames[envelope.TypeId], envelope.SerializerId)
+ if err != nil {
+ plog.Error("EndpointReader failed to deserialize", log.Error(err))
+ return err
+ }
+
+ // translate from on-the-wire representation to in-process representation
+ // this only applies to root level messages, and never on nested child messages
+ if v, ok := message.(RootSerialized); ok {
+ message = v.Deserialize()
+ }
+
+ switch msg := message.(type) {
+ case *actor.Terminated:
+ rt := &remoteTerminate{
+ Watchee: msg.Who,
+ Watcher: target,
+ }
+ s.remote.edpManager.remoteTerminate(rt)
+ case actor.SystemMessage:
+ ref, _ := s.remote.actorSystem.ProcessRegistry.GetLocal(target.ID)
+ ref.SendSystemMessage(target, msg)
+ default:
+ var header map[string]string
+
+ // fast path
+ if sender == nil && envelope.MessageHeader == nil {
+ s.remote.actorSystem.Root.Send(target, message)
+ continue
+ }
+
+ // slow path
+ if envelope.MessageHeader != nil {
+ header = envelope.MessageHeader.HeaderData
+ }
+ localEnvelope := &actor.MessageEnvelope{
+ Header: header,
+ Message: message,
+ Sender: sender,
+ }
+ s.remote.actorSystem.Root.Send(target, localEnvelope)
+ }
+ }
+ return nil
+}
+
+func deserializeSender(pid *actor.PID, index int32, requestId uint32, arr []*actor.PID) *actor.PID {
+ if index == 0 {
+ pid = nil
+ } else {
+ pid = arr[index-1]
+
+ // if request id is used. make sure to clone the PID first, so we don't corrupt the lookup
+ if requestId > 0 {
+ pid, _ = proto.Clone(pid).(*actor.PID)
+ pid.RequestID = requestId
+ }
+ }
+ return pid
+}
+
+func deserializeTarget(pid *actor.PID, index int32, requestId uint32, arr []*actor.PID) *actor.PID {
+ pid = arr[index]
+
+ // if request id is used. make sure to clone the PID first, so we don't corrupt the lookup
+ if requestId > 0 {
+ pid, _ = proto.Clone(pid).(*actor.PID)
+ pid.RequestID = requestId
+ }
+
+ return pid
+}
+
+func (s *endpointReader) onServerConnection(stream Remoting_ReceiveServer, sc *ServerConnection) {
+ if s.remote.BlockList().IsBlocked(sc.SystemId) {
+ plog.Debug("EndpointReader is blocked", log.String("systemId", sc.SystemId))
+
+ err := stream.Send(
+ &RemoteMessage{
+ MessageType: &RemoteMessage_ConnectResponse{
+ ConnectResponse: &ConnectResponse{
+ Blocked: true,
+ MemberId: s.remote.actorSystem.ID,
+ },
+ },
+ })
+ if err != nil {
+ plog.Error("EndpointReader failed to send ConnectResponse message", log.Error(err))
+ }
+
+ address := sc.Address
+ systemID := sc.SystemId
+
+ // TODO
+ _ = address
+ _ = systemID
+ } else {
+ err := stream.Send(
+ &RemoteMessage{
+ MessageType: &RemoteMessage_ConnectResponse{
+ ConnectResponse: &ConnectResponse{
+ Blocked: false,
+ MemberId: s.remote.actorSystem.ID,
+ },
+ },
+ })
+ if err != nil {
+ plog.Error("EndpointReader failed to send ConnectResponse message", log.Error(err))
+ }
+ }
+}
+
+func (s *endpointReader) suspend(toSuspend bool) {
+ s.suspended = toSuspend
+ if toSuspend {
+ plog.Debug("Suspended EndpointReader")
+ }
+}
diff --git a/remote/endpoint_watcher.go b/remote/endpoint_watcher.go
new file mode 100644
index 0000000000000000000000000000000000000000..d05cc85c6c6a1996638e7e5123a6b67688a76d4a
--- /dev/null
+++ b/remote/endpoint_watcher.go
@@ -0,0 +1,152 @@
+package remote
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+func newEndpointWatcher(remote *Remote, address string) actor.Producer {
+ return func() actor.Actor {
+ watcher := &endpointWatcher{
+ behavior: actor.NewBehavior(),
+ address: address,
+ remote: remote,
+ }
+ watcher.behavior.Become(watcher.connected)
+ return watcher
+ }
+}
+
+type endpointWatcher struct {
+ behavior actor.Behavior
+ address string
+ watched map[string]*actor.PIDSet // key is the watching PID string, value is the watched PID
+ remote *Remote
+}
+
+func (state *endpointWatcher) initialize() {
+ plog.Info("Started EndpointWatcher", log.String("address", state.address))
+ state.watched = make(map[string]*actor.PIDSet)
+}
+
+func (state *endpointWatcher) Receive(ctx actor.Context) {
+ state.behavior.Receive(ctx)
+}
+
+func (state *endpointWatcher) connected(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ state.initialize()
+
+ case *remoteTerminate:
+ // delete the watch entries
+ if pidSet, ok := state.watched[msg.Watcher.ID]; ok {
+ pidSet.Remove(msg.Watchee)
+ if pidSet.Len() == 0 {
+ delete(state.watched, msg.Watcher.ID)
+ }
+ }
+
+ terminated := &actor.Terminated{
+ Who: msg.Watchee,
+ Why: actor.TerminatedReason_Stopped,
+ }
+ ref, ok := state.remote.actorSystem.ProcessRegistry.GetLocal(msg.Watcher.ID)
+ if ok {
+ ref.SendSystemMessage(msg.Watcher, terminated)
+ }
+ case *EndpointConnectedEvent:
+ // Already connected, pass
+ case *EndpointTerminatedEvent:
+ plog.Info("EndpointWatcher handling terminated",
+ log.String("address", state.address), log.Int("watched", len(state.watched)))
+
+ for id, pidSet := range state.watched {
+ // try to find the watcher ExtensionID in the local actor registry
+ ref, ok := state.remote.actorSystem.ProcessRegistry.GetLocal(id)
+ if ok {
+ pidSet.ForEach(func(i int, pid *actor.PID) {
+ // create a terminated event for the Watched actor
+ terminated := &actor.Terminated{
+ Who: pid,
+ Why: actor.TerminatedReason_AddressTerminated,
+ }
+
+ watcher := state.remote.actorSystem.NewLocalPID(id)
+ // send the address Terminated event to the Watcher
+ ref.SendSystemMessage(watcher, terminated)
+ })
+ }
+ }
+
+ // Clear watcher's map
+ state.watched = make(map[string]*actor.PIDSet)
+ state.behavior.Become(state.terminated)
+ ctx.Stop(ctx.Self())
+
+ case *remoteWatch:
+ // add watchee to watcher's map
+ if pidSet, ok := state.watched[msg.Watcher.ID]; ok {
+ pidSet.Add(msg.Watchee)
+ } else {
+ state.watched[msg.Watcher.ID] = actor.NewPIDSet(msg.Watchee)
+ }
+
+ // recreate the Watch command
+ w := &actor.Watch{
+ Watcher: msg.Watcher,
+ }
+
+ // pass it off to the remote PID
+ state.remote.SendMessage(msg.Watchee, nil, w, nil, -1)
+
+ case *remoteUnwatch:
+ // delete the watch entries
+ if pidSet, ok := state.watched[msg.Watcher.ID]; ok {
+ pidSet.Remove(msg.Watchee)
+ if pidSet.Len() == 0 {
+ delete(state.watched, msg.Watcher.ID)
+ }
+ }
+
+ // recreate the Unwatch command
+ uw := &actor.Unwatch{
+ Watcher: msg.Watcher,
+ }
+
+ // pass it off to the remote PID
+ state.remote.SendMessage(msg.Watchee, nil, uw, nil, -1)
+ case actor.SystemMessage, actor.AutoReceiveMessage:
+ // ignore
+ default:
+ plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.Message(msg))
+ }
+}
+
+func (state *endpointWatcher) terminated(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *remoteWatch:
+ // try to find the watcher ExtensionID in the local actor registry
+ ref, ok := state.remote.actorSystem.ProcessRegistry.GetLocal(msg.Watcher.ID)
+
+ if ok {
+ // create a terminated event for the Watched actor
+ terminated := &actor.Terminated{
+ Who: msg.Watchee,
+ Why: actor.TerminatedReason_AddressTerminated,
+ }
+ // send the address Terminated event to the Watcher
+ ref.SendSystemMessage(msg.Watcher, terminated)
+ }
+ case *EndpointConnectedEvent:
+ plog.Info("EndpointWatcher handling restart", log.String("address", state.address))
+ state.behavior.Become(state.connected)
+ case *remoteTerminate, *EndpointTerminatedEvent, *remoteUnwatch:
+ // pass
+ plog.Error("EndpointWatcher receive message for already terminated endpoint", log.String("address", state.address), log.Message(msg))
+ case actor.SystemMessage, actor.AutoReceiveMessage:
+ // ignore
+ default:
+ plog.Error("EndpointWatcher received unknown message", log.String("address", state.address), log.TypeOf("type", msg), log.Message(msg))
+ }
+}
diff --git a/remote/endpoint_writer.go b/remote/endpoint_writer.go
new file mode 100644
index 0000000000000000000000000000000000000000..740ec7f8e861cc369a911df7b79f6575596256c7
--- /dev/null
+++ b/remote/endpoint_writer.go
@@ -0,0 +1,338 @@
+package remote
+
+import (
+ "errors"
+ "io"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "golang.org/x/net/context"
+ "google.golang.org/grpc"
+ "google.golang.org/protobuf/proto"
+)
+
+func endpointWriterProducer(remote *Remote, address string, config *Config) actor.Producer {
+ return func() actor.Actor {
+ return &endpointWriter{
+ address: address,
+ config: config,
+ remote: remote,
+ }
+ }
+}
+
+type endpointWriter struct {
+ config *Config
+ address string
+ conn *grpc.ClientConn
+ stream Remoting_ReceiveClient
+ remote *Remote
+}
+
+type restartAfterConnectFailure struct {
+ err error
+}
+
+func (state *endpointWriter) initialize(ctx actor.Context) {
+ now := time.Now()
+ plog.Info("Started EndpointWriter. connecting", log.String("address", state.address))
+
+ var err error
+
+ for i := 0; i < state.remote.config.MaxRetryCount; i++ {
+ err = state.initializeInternal()
+ if err != nil {
+ plog.Error("EndpointWriter failed to connect", log.String("address", state.address), log.Error(err), log.Int("retry", i))
+ // Wait 2 seconds to restart and retry
+ // Replace with Exponential Backoff
+ time.Sleep(2 * time.Second)
+ continue
+ }
+
+ break
+ }
+
+ if err != nil {
+ terminated := &EndpointTerminatedEvent{
+ Address: state.address,
+ }
+ state.remote.actorSystem.EventStream.Publish(terminated)
+
+ return
+
+ // plog.Error("EndpointWriter failed to connect", log.String("address", state.address), log.Error(err))
+
+ // Wait 2 seconds to restart and retry
+ // TODO: Replace with Exponential Backoff
+ // send this as a message to self - do not block the mailbox processing
+ // if in the meantime the actor is stopped (EndpointTerminated event), the message will be ignored (deadlettered)
+ // TODO: would it be a better idea to just publish EndpointTerminatedEvent here? to use the same path as when the connection is lost?
+ // time.AfterFunc(2*time.Second, func() {
+ // ctx.Send(ctx.Self(), &restartAfterConnectFailure{err})
+ // })
+
+ }
+
+ plog.Info("EndpointWriter connected", log.String("address", state.address), log.Duration("cost", time.Since(now)))
+}
+
+func (state *endpointWriter) initializeInternal() error {
+ conn, err := grpc.Dial(state.address, state.config.DialOptions...)
+ if err != nil {
+ return err
+ }
+ state.conn = conn
+ c := NewRemotingClient(conn)
+ stream, err := c.Receive(context.Background(), state.config.CallOptions...)
+ if err != nil {
+ plog.Error("EndpointWriter failed to create receive stream", log.String("address", state.address), log.Error(err))
+ return err
+ }
+ state.stream = stream
+
+ err = stream.Send(&RemoteMessage{
+ MessageType: &RemoteMessage_ConnectRequest{
+ ConnectRequest: &ConnectRequest{
+ ConnectionType: &ConnectRequest_ServerConnection{
+ ServerConnection: &ServerConnection{
+ SystemId: state.remote.actorSystem.ID,
+ Address: state.remote.actorSystem.Address(),
+ },
+ },
+ },
+ },
+ })
+ if err != nil {
+ plog.Error("EndpointWriter failed to send connect request", log.String("address", state.address), log.Error(err))
+ return err
+ }
+
+ connection, err := stream.Recv()
+ if err != nil {
+ plog.Error("EndpointWriter failed to receive connect response", log.String("address", state.address), log.Error(err))
+ return err
+ }
+
+ switch connection.MessageType.(type) {
+ case *RemoteMessage_ConnectResponse:
+ plog.Debug("Received connect response", log.String("fromAddress", state.address))
+ // TODO: handle blocked status received from remote server
+ break
+ default:
+ plog.Error("EndpointWriter got invalid connect response", log.String("address", state.address), log.TypeOf("type", connection.MessageType))
+ return errors.New("invalid connect response")
+ }
+
+ go func() {
+ for {
+ _, err := stream.Recv()
+ switch {
+ case errors.Is(err, io.EOF):
+ plog.Debug("EndpointWriter stream completed", log.String("address", state.address))
+ return
+ case err != nil:
+ plog.Error("EndpointWriter lost connection", log.String("address", state.address), log.Error(err))
+ terminated := &EndpointTerminatedEvent{
+ Address: state.address,
+ }
+ state.remote.actorSystem.EventStream.Publish(terminated)
+ return
+ default: // DisconnectRequest
+ plog.Info("EndpointWriter got DisconnectRequest form remote", log.String("address", state.address))
+ terminated := &EndpointTerminatedEvent{
+ Address: state.address,
+ }
+ state.remote.actorSystem.EventStream.Publish(terminated)
+ }
+ }
+ }()
+
+ connected := &EndpointConnectedEvent{Address: state.address}
+ state.remote.actorSystem.EventStream.Publish(connected)
+ return nil
+}
+
+func (state *endpointWriter) sendEnvelopes(msg []interface{}, ctx actor.Context) {
+ envelopes := make([]*MessageEnvelope, len(msg))
+
+ // type name uniqueness map name string to type index
+ typeNames := make(map[string]int32)
+ typeNamesArr := make([]string, 0)
+
+ targetNames := make(map[string]int32)
+ targetNamesArr := make([]*actor.PID, 0)
+
+ senderNames := make(map[string]int32)
+ senderNamesArr := make([]*actor.PID, 0)
+
+ var (
+ header *MessageHeader
+ typeID int32
+ targetID int32
+ senderID int32
+ serializerID int32
+ )
+
+ for i, tmp := range msg {
+ switch unwrapped := tmp.(type) {
+ case *EndpointTerminatedEvent, EndpointTerminatedEvent:
+ plog.Debug("Handling array wrapped terminate event", log.String("address", state.address), log.Object("msg", unwrapped))
+ ctx.Stop(ctx.Self())
+ return
+ }
+
+ rd, _ := tmp.(*remoteDeliver)
+
+ if state.stream == nil { // not connected yet since first connection attempt failed and we are waiting for the retry
+ if rd.sender != nil {
+ state.remote.actorSystem.Root.Send(rd.sender, &actor.DeadLetterResponse{Target: rd.target})
+ } else {
+ state.remote.actorSystem.EventStream.Publish(&actor.DeadLetterEvent{Message: rd.message, Sender: rd.sender, PID: rd.target})
+ }
+ continue
+ }
+
+ if rd.header == nil || rd.header.Length() == 0 {
+ header = nil
+ } else {
+ header = &MessageHeader{
+ HeaderData: rd.header.ToMap(),
+ }
+ }
+
+ // if the message can be translated to a serialization representation, we do this here
+ // this only apply to root level messages and never to nested child objects inside the message
+ message := rd.message
+ if v, ok := message.(RootSerializable); ok {
+ message = v.Serialize()
+ }
+
+ bytes, typeName, err := Serialize(message, serializerID)
+ if err != nil {
+ panic(err)
+ }
+ typeID, typeNamesArr = addToLookup(typeNames, typeName, typeNamesArr)
+ targetID, targetNamesArr = addToTargetLookup(targetNames, rd.target, targetNamesArr)
+ targetRequestID := rd.target.RequestID
+
+ senderID, senderNamesArr = addToSenderLookup(senderNames, rd.sender, senderNamesArr)
+ senderRequestID := uint32(0)
+ if rd.sender != nil {
+ senderRequestID = rd.sender.RequestID
+ }
+
+ envelopes[i] = &MessageEnvelope{
+ MessageHeader: header,
+ MessageData: bytes,
+ Sender: senderID,
+ Target: targetID,
+ TypeId: typeID,
+ SerializerId: serializerID,
+ TargetRequestId: targetRequestID,
+ SenderRequestId: senderRequestID,
+ }
+ }
+
+ err := state.stream.Send(&RemoteMessage{
+ MessageType: &RemoteMessage_MessageBatch{
+ MessageBatch: &MessageBatch{
+ TypeNames: typeNamesArr,
+ Targets: targetNamesArr,
+ Senders: senderNamesArr,
+ Envelopes: envelopes,
+ },
+ },
+ })
+ if err != nil {
+ ctx.Stash()
+ plog.Debug("gRPC Failed to send", log.String("address", state.address), log.Error(err))
+ panic("restart it")
+ }
+}
+
+func addToLookup(m map[string]int32, name string, a []string) (int32, []string) {
+ max := int32(len(m))
+ id, ok := m[name]
+ if !ok {
+ m[name] = max
+ id = max
+ a = append(a, name)
+ }
+ return id, a
+}
+
+func addToTargetLookup(m map[string]int32, pid *actor.PID, arr []*actor.PID) (int32, []*actor.PID) {
+ max := int32(len(m))
+ key := pid.Address + "/" + pid.ID
+ id, ok := m[key]
+ if !ok {
+ c, _ := proto.Clone(pid).(*actor.PID)
+ c.RequestID = 0
+ m[key] = max
+ id = max
+ arr = append(arr, c)
+ }
+ return id, arr
+}
+
+func addToSenderLookup(m map[string]int32, pid *actor.PID, arr []*actor.PID) (int32, []*actor.PID) {
+ if pid == nil {
+ return 0, arr
+ }
+
+ max := int32(len(m))
+ key := pid.Address + "/" + pid.ID
+ id, ok := m[key]
+ if !ok {
+ c, _ := proto.Clone(pid).(*actor.PID)
+ c.RequestID = 0
+ m[key] = max
+ id = max
+ arr = append(arr, c)
+ }
+ return id + 1, arr
+}
+
+func (state *endpointWriter) Receive(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case *actor.Started:
+ state.initialize(ctx)
+ case *actor.Stopped:
+ plog.Debug("EndpointWriter stopped", log.String("address", state.address))
+ state.closeClientConn()
+ case *actor.Restarting:
+ plog.Debug("EndpointWriter restarting", log.String("address", state.address))
+ state.closeClientConn()
+ case *EndpointTerminatedEvent:
+ plog.Info("EndpointWriter received EndpointTerminatedEvent, stopping", log.String("address", state.address))
+ ctx.Stop(ctx.Self())
+ case *restartAfterConnectFailure:
+ plog.Debug("EndpointWriter initiating self-restart after failing to connect and a delay", log.String("address", state.address))
+ panic(msg.err)
+ case []interface{}:
+ state.sendEnvelopes(msg, ctx)
+ case actor.SystemMessage, actor.AutoReceiveMessage:
+ // ignore
+ default:
+ plog.Error("EndpointWriter received unknown message", log.String("address", state.address), log.TypeOf("type", msg), log.Message(msg))
+ }
+}
+
+func (state *endpointWriter) closeClientConn() {
+ plog.Info("EndpointWriter closing client connection", log.String("address", state.address))
+ if state.stream != nil {
+ err := state.stream.CloseSend()
+ if err != nil {
+ plog.Error("EndpointWriter error when closing the stream", log.Error(err))
+ }
+ state.stream = nil
+ }
+ if state.conn != nil {
+ err := state.conn.Close()
+ if err != nil {
+ plog.Error("EndpointWriter error when closing the client conn", log.Error(err))
+ }
+ state.conn = nil
+ }
+}
diff --git a/remote/endpoint_writer_mailbox.go b/remote/endpoint_writer_mailbox.go
new file mode 100644
index 0000000000000000000000000000000000000000..19bcfee397c956a325d8ce68e6c90777a7b2c63e
--- /dev/null
+++ b/remote/endpoint_writer_mailbox.go
@@ -0,0 +1,135 @@
+package remote
+
+import (
+ "runtime"
+ "sync/atomic"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/goring"
+ "gitee.com/simplexyz/simpleactor-go/internal/queue/mpsc"
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+const (
+ mailboxIdle int32 = iota
+ mailboxRunning int32 = iota
+)
+
+const (
+ mailboxHasNoMessages int32 = iota
+ mailboxHasMoreMessages int32 = iota
+)
+
+type endpointWriterMailbox struct {
+ userMailbox *goring.Queue
+ systemMailbox *mpsc.Queue
+ schedulerStatus int32
+ hasMoreMessages int32
+ invoker actor.MessageInvoker
+ batchSize int
+ dispatcher actor.Dispatcher
+ suspended bool
+}
+
+func (m *endpointWriterMailbox) PostUserMessage(message interface{}) {
+ // batching mailbox only use the message part
+ m.userMailbox.Push(message)
+ m.schedule()
+}
+
+func (m *endpointWriterMailbox) PostSystemMessage(message interface{}) {
+ m.systemMailbox.Push(message)
+ m.schedule()
+}
+
+func (m *endpointWriterMailbox) RegisterHandlers(invoker actor.MessageInvoker, dispatcher actor.Dispatcher) {
+ m.invoker = invoker
+ m.dispatcher = dispatcher
+}
+
+func (m *endpointWriterMailbox) Start() {
+}
+
+func (m *endpointWriterMailbox) schedule() {
+ atomic.StoreInt32(&m.hasMoreMessages, mailboxHasMoreMessages) // we have more messages to process
+ if atomic.CompareAndSwapInt32(&m.schedulerStatus, mailboxIdle, mailboxRunning) {
+ m.dispatcher.Schedule(m.processMessages)
+ }
+}
+
+func (m *endpointWriterMailbox) processMessages() {
+ // we are about to start processing messages, we can safely reset the message flag of the mailbox
+ atomic.StoreInt32(&m.hasMoreMessages, mailboxHasNoMessages)
+process:
+ m.run()
+
+ // set mailbox to idle
+ atomic.StoreInt32(&m.schedulerStatus, mailboxIdle)
+
+ // check if there are still messages to process (sent after the message loop ended)
+ if atomic.SwapInt32(&m.hasMoreMessages, mailboxHasNoMessages) == mailboxHasMoreMessages {
+ // try setting the mailbox back to running
+ if atomic.CompareAndSwapInt32(&m.schedulerStatus, mailboxIdle, mailboxRunning) {
+ goto process
+ }
+ }
+}
+
+func (m *endpointWriterMailbox) run() {
+ var msg interface{}
+ defer func() {
+ if r := recover(); r != nil {
+ plog.Info("[ACTOR] Recovering", log.Object("actor", m.invoker), log.Object("reason", r), log.Stack())
+ m.invoker.EscalateFailure(r, msg)
+ }
+ }()
+
+ for {
+ // keep processing system messages until queue is empty
+ if msg = m.systemMailbox.Pop(); msg != nil {
+ switch msg.(type) {
+ case *actor.SuspendMailbox:
+ m.suspended = true
+ case *actor.ResumeMailbox:
+ m.suspended = false
+ default:
+ m.invoker.InvokeSystemMessage(msg)
+ }
+
+ continue
+ }
+
+ // didn't process a system message, so break until we are resumed
+ if m.suspended {
+ return
+ }
+
+ var ok bool
+ if msg, ok = m.userMailbox.PopMany(int64(m.batchSize)); ok {
+ m.invoker.InvokeUserMessage(msg)
+ } else {
+ return
+ }
+
+ runtime.Gosched()
+ }
+}
+
+func (m *endpointWriterMailbox) UserMessageCount() int {
+ return int(m.userMailbox.Length())
+}
+
+func endpointWriterMailboxProducer(batchSize, initialSize int) actor.MailboxProducer {
+ return func() actor.Mailbox {
+ userMailbox := goring.New(int64(initialSize))
+ systemMailbox := mpsc.New()
+ return &endpointWriterMailbox{
+ userMailbox: userMailbox,
+ systemMailbox: systemMailbox,
+ hasMoreMessages: mailboxHasNoMessages,
+ schedulerStatus: mailboxIdle,
+ batchSize: batchSize,
+ }
+ }
+}
diff --git a/remote/errors.go b/remote/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..e34314466499fcb910126ccc463cf557c5a41d2d
--- /dev/null
+++ b/remote/errors.go
@@ -0,0 +1,25 @@
+package remote
+
+var (
+ ErrUnAvailable = &ResponseError{ResponseStatusCodeUNAVAILABLE}
+ ErrTimeout = &ResponseError{ResponseStatusCodeTIMEOUT}
+ ErrProcessNameAlreadyExist = &ResponseError{ResponseStatusCodePROCESSNAMEALREADYEXIST}
+ ErrDeadLetter = &ResponseError{ResponseStatusCodeDeadLetter}
+ ErrUnknownError = &ResponseError{ResponseStatusCodeERROR}
+)
+
+// ResponseError is an error type.
+// e.g.:
+//
+// var err = &ResponseError{1}
+type ResponseError struct {
+ Code ResponseStatusCode
+}
+
+func (r *ResponseError) Error() string {
+ if r == nil {
+ return "nil"
+ }
+
+ return r.Code.String()
+}
diff --git a/remote/json_serializer.go b/remote/json_serializer.go
new file mode 100644
index 0000000000000000000000000000000000000000..86221828b9fa300ca49599a11213a51580f4993a
--- /dev/null
+++ b/remote/json_serializer.go
@@ -0,0 +1,74 @@
+package remote
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+
+ "github.com/gogo/protobuf/jsonpb"
+ "github.com/gogo/protobuf/proto"
+)
+
+type jsonSerializer struct {
+ jsonpb.Marshaler
+ jsonpb.Unmarshaler
+}
+
+func newJsonSerializer() Serializer {
+ return &jsonSerializer{
+ Marshaler: jsonpb.Marshaler{},
+ Unmarshaler: jsonpb.Unmarshaler{
+ AllowUnknownFields: true,
+ },
+ }
+}
+
+func (j *jsonSerializer) Serialize(msg interface{}) ([]byte, error) {
+ if message, ok := msg.(*JsonMessage); ok {
+ return []byte(message.Json), nil
+ } else if message, ok := msg.(proto.Message); ok {
+
+ str, err := j.Marshaler.MarshalToString(message)
+ if err != nil {
+ return nil, err
+ }
+
+ return []byte(str), nil
+ }
+ return nil, fmt.Errorf("msg must be proto.Message")
+}
+
+func (j *jsonSerializer) Deserialize(typeName string, b []byte) (interface{}, error) {
+ protoType := proto.MessageType(typeName)
+ if protoType == nil {
+ m := &JsonMessage{
+ TypeName: typeName,
+ Json: string(b),
+ }
+ return m, nil
+ }
+ t := protoType.Elem()
+
+ intPtr := reflect.New(t)
+ instance, ok := intPtr.Interface().(proto.Message)
+ if ok {
+ r := bytes.NewReader(b)
+ j.Unmarshaler.Unmarshal(r, instance)
+
+ return instance, nil
+ }
+
+ return nil, fmt.Errorf("msg must be proto.Message")
+}
+
+func (j *jsonSerializer) GetTypeName(msg interface{}) (string, error) {
+ if message, ok := msg.(*JsonMessage); ok {
+ return message.TypeName, nil
+ } else if message, ok := msg.(proto.Message); ok {
+ typeName := proto.MessageName(message)
+
+ return typeName, nil
+ }
+
+ return "", fmt.Errorf("msg must be proto.Message")
+}
diff --git a/remote/kind.go b/remote/kind.go
new file mode 100644
index 0000000000000000000000000000000000000000..49512961144aa8ae0f6a3aa3ea1a90ba58ff4c4c
--- /dev/null
+++ b/remote/kind.go
@@ -0,0 +1,17 @@
+package remote
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+// Kind is the configuration for a kind
+type Kind struct {
+ Kind string
+ Props *actor.Props
+}
+
+// NewKind creates a new kind configuration
+func NewKind(kind string, props *actor.Props) *Kind {
+ return &Kind{
+ Kind: kind,
+ Props: props,
+ }
+}
diff --git a/remote/log.go b/remote/log.go
new file mode 100644
index 0000000000000000000000000000000000000000..735676394f9a2b2a7d78f6f2731484c9175f099c
--- /dev/null
+++ b/remote/log.go
@@ -0,0 +1,14 @@
+package remote
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/log"
+)
+
+var plog = log.New(log.DebugLevel, "[REMOTE]")
+
+// SetLogLevel sets the log level for the logger.
+//
+// SetLogLevel is safe to call concurrently
+func SetLogLevel(level log.Level) {
+ plog.SetLevel(level)
+}
diff --git a/remote/messages.go b/remote/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..de7a01e61ee6c5337be4bac246828e07c21323c7
--- /dev/null
+++ b/remote/messages.go
@@ -0,0 +1,55 @@
+package remote
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+type EndpointTerminatedEvent struct {
+ Address string
+}
+
+type EndpointConnectedEvent struct {
+ Address string
+}
+
+type remoteWatch struct {
+ Watcher *actor.PID
+ Watchee *actor.PID
+}
+
+type remoteUnwatch struct {
+ Watcher *actor.PID
+ Watchee *actor.PID
+}
+
+type remoteDeliver struct {
+ header actor.ReadonlyMessageHeader
+ message interface{}
+ target *actor.PID
+ sender *actor.PID
+ serializerID int32
+}
+
+type remoteTerminate struct {
+ Watcher *actor.PID
+ Watchee *actor.PID
+}
+
+type JsonMessage struct {
+ TypeName string
+ Json string
+}
+
+var stopMessage interface{} = &actor.Stop{}
+
+var (
+ ActorPidRespErr interface{} = &ActorPidResponse{StatusCode: ResponseStatusCodeERROR.ToInt32()}
+ ActorPidRespTimeout interface{} = &ActorPidResponse{StatusCode: ResponseStatusCodeTIMEOUT.ToInt32()}
+ ActorPidRespUnavailable interface{} = &ActorPidResponse{StatusCode: ResponseStatusCodeUNAVAILABLE.ToInt32()}
+)
+
+type (
+ // Ping is message sent by the actor system to probe an actor is started.
+ Ping struct{}
+
+ // Pong is response for ping.
+ Pong struct{}
+)
diff --git a/remote/proto_serializer.go b/remote/proto_serializer.go
new file mode 100644
index 0000000000000000000000000000000000000000..a85ade95078282ab832455b51797e56737cc0003
--- /dev/null
+++ b/remote/proto_serializer.go
@@ -0,0 +1,45 @@
+package remote
+
+import (
+ "fmt"
+
+ "google.golang.org/protobuf/proto"
+ "google.golang.org/protobuf/reflect/protoreflect"
+ "google.golang.org/protobuf/reflect/protoregistry"
+)
+
+type protoSerializer struct{}
+
+func newProtoSerializer() *protoSerializer {
+ return &protoSerializer{}
+}
+
+func (p *protoSerializer) Serialize(msg interface{}) ([]byte, error) {
+ if message, ok := msg.(proto.Message); ok {
+ bytes, err := proto.Marshal(message)
+ if err != nil {
+ return nil, err
+ }
+
+ return bytes, nil
+ }
+ return nil, fmt.Errorf("msg must be proto.Message")
+}
+
+func (p *protoSerializer) Deserialize(typeName string, bytes []byte) (interface{}, error) {
+ n, _ := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(typeName))
+
+ pm := n.New().Interface()
+
+ err := proto.Unmarshal(bytes, pm)
+ return pm, err
+}
+
+func (protoSerializer) GetTypeName(msg interface{}) (string, error) {
+ if message, ok := msg.(proto.Message); ok {
+ typeName := proto.MessageName(message)
+
+ return string(typeName), nil
+ }
+ return "", fmt.Errorf("msg must be proto.Message")
+}
diff --git a/remote/remote.pb.go b/remote/remote.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..afa715489141cf490a7bbf4305b8e16e821156a3
--- /dev/null
+++ b/remote/remote.pb.go
@@ -0,0 +1,1407 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: remote.proto
+
+package remote
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ListProcessesMatchType int32
+
+const (
+ ListProcessesMatchType_MatchPartOfString ListProcessesMatchType = 0
+ ListProcessesMatchType_MatchExactString ListProcessesMatchType = 1
+ ListProcessesMatchType_MatchRegex ListProcessesMatchType = 2
+)
+
+// Enum value maps for ListProcessesMatchType.
+var (
+ ListProcessesMatchType_name = map[int32]string{
+ 0: "MatchPartOfString",
+ 1: "MatchExactString",
+ 2: "MatchRegex",
+ }
+ ListProcessesMatchType_value = map[string]int32{
+ "MatchPartOfString": 0,
+ "MatchExactString": 1,
+ "MatchRegex": 2,
+ }
+)
+
+func (x ListProcessesMatchType) Enum() *ListProcessesMatchType {
+ p := new(ListProcessesMatchType)
+ *p = x
+ return p
+}
+
+func (x ListProcessesMatchType) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ListProcessesMatchType) Descriptor() protoreflect.EnumDescriptor {
+ return file_remote_proto_enumTypes[0].Descriptor()
+}
+
+func (ListProcessesMatchType) Type() protoreflect.EnumType {
+ return &file_remote_proto_enumTypes[0]
+}
+
+func (x ListProcessesMatchType) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ListProcessesMatchType.Descriptor instead.
+func (ListProcessesMatchType) EnumDescriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{0}
+}
+
+type RemoteMessage struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Types that are assignable to MessageType:
+ // *RemoteMessage_MessageBatch
+ // *RemoteMessage_ConnectRequest
+ // *RemoteMessage_ConnectResponse
+ // *RemoteMessage_DisconnectRequest
+ MessageType isRemoteMessage_MessageType `protobuf_oneof:"message_type"`
+}
+
+func (x *RemoteMessage) Reset() {
+ *x = RemoteMessage{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoteMessage) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteMessage) ProtoMessage() {}
+
+func (x *RemoteMessage) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteMessage.ProtoReflect.Descriptor instead.
+func (*RemoteMessage) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{0}
+}
+
+func (m *RemoteMessage) GetMessageType() isRemoteMessage_MessageType {
+ if m != nil {
+ return m.MessageType
+ }
+ return nil
+}
+
+func (x *RemoteMessage) GetMessageBatch() *MessageBatch {
+ if x, ok := x.GetMessageType().(*RemoteMessage_MessageBatch); ok {
+ return x.MessageBatch
+ }
+ return nil
+}
+
+func (x *RemoteMessage) GetConnectRequest() *ConnectRequest {
+ if x, ok := x.GetMessageType().(*RemoteMessage_ConnectRequest); ok {
+ return x.ConnectRequest
+ }
+ return nil
+}
+
+func (x *RemoteMessage) GetConnectResponse() *ConnectResponse {
+ if x, ok := x.GetMessageType().(*RemoteMessage_ConnectResponse); ok {
+ return x.ConnectResponse
+ }
+ return nil
+}
+
+func (x *RemoteMessage) GetDisconnectRequest() *DisconnectRequest {
+ if x, ok := x.GetMessageType().(*RemoteMessage_DisconnectRequest); ok {
+ return x.DisconnectRequest
+ }
+ return nil
+}
+
+type isRemoteMessage_MessageType interface {
+ isRemoteMessage_MessageType()
+}
+
+type RemoteMessage_MessageBatch struct {
+ MessageBatch *MessageBatch `protobuf:"bytes,1,opt,name=message_batch,json=messageBatch,proto3,oneof"`
+}
+
+type RemoteMessage_ConnectRequest struct {
+ ConnectRequest *ConnectRequest `protobuf:"bytes,2,opt,name=connect_request,json=connectRequest,proto3,oneof"`
+}
+
+type RemoteMessage_ConnectResponse struct {
+ ConnectResponse *ConnectResponse `protobuf:"bytes,3,opt,name=connect_response,json=connectResponse,proto3,oneof"`
+}
+
+type RemoteMessage_DisconnectRequest struct {
+ DisconnectRequest *DisconnectRequest `protobuf:"bytes,4,opt,name=disconnect_request,json=disconnectRequest,proto3,oneof"`
+}
+
+func (*RemoteMessage_MessageBatch) isRemoteMessage_MessageType() {}
+
+func (*RemoteMessage_ConnectRequest) isRemoteMessage_MessageType() {}
+
+func (*RemoteMessage_ConnectResponse) isRemoteMessage_MessageType() {}
+
+func (*RemoteMessage_DisconnectRequest) isRemoteMessage_MessageType() {}
+
+type MessageBatch struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TypeNames []string `protobuf:"bytes,1,rep,name=type_names,json=typeNames,proto3" json:"type_names,omitempty"`
+ Targets []*actor.PID `protobuf:"bytes,2,rep,name=targets,proto3" json:"targets,omitempty"`
+ Envelopes []*MessageEnvelope `protobuf:"bytes,3,rep,name=envelopes,proto3" json:"envelopes,omitempty"`
+ Senders []*actor.PID `protobuf:"bytes,4,rep,name=senders,proto3" json:"senders,omitempty"`
+}
+
+func (x *MessageBatch) Reset() {
+ *x = MessageBatch{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MessageBatch) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageBatch) ProtoMessage() {}
+
+func (x *MessageBatch) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageBatch.ProtoReflect.Descriptor instead.
+func (*MessageBatch) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *MessageBatch) GetTypeNames() []string {
+ if x != nil {
+ return x.TypeNames
+ }
+ return nil
+}
+
+func (x *MessageBatch) GetTargets() []*actor.PID {
+ if x != nil {
+ return x.Targets
+ }
+ return nil
+}
+
+func (x *MessageBatch) GetEnvelopes() []*MessageEnvelope {
+ if x != nil {
+ return x.Envelopes
+ }
+ return nil
+}
+
+func (x *MessageBatch) GetSenders() []*actor.PID {
+ if x != nil {
+ return x.Senders
+ }
+ return nil
+}
+
+type MessageEnvelope struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ TypeId int32 `protobuf:"varint,1,opt,name=type_id,json=typeId,proto3" json:"type_id,omitempty"`
+ MessageData []byte `protobuf:"bytes,2,opt,name=message_data,json=messageData,proto3" json:"message_data,omitempty"`
+ Target int32 `protobuf:"varint,3,opt,name=target,proto3" json:"target,omitempty"`
+ Sender int32 `protobuf:"varint,4,opt,name=sender,proto3" json:"sender,omitempty"`
+ SerializerId int32 `protobuf:"varint,5,opt,name=serializer_id,json=serializerId,proto3" json:"serializer_id,omitempty"`
+ MessageHeader *MessageHeader `protobuf:"bytes,6,opt,name=message_header,json=messageHeader,proto3" json:"message_header,omitempty"`
+ TargetRequestId uint32 `protobuf:"varint,7,opt,name=target_request_id,json=targetRequestId,proto3" json:"target_request_id,omitempty"`
+ SenderRequestId uint32 `protobuf:"varint,8,opt,name=sender_request_id,json=senderRequestId,proto3" json:"sender_request_id,omitempty"`
+}
+
+func (x *MessageEnvelope) Reset() {
+ *x = MessageEnvelope{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MessageEnvelope) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageEnvelope) ProtoMessage() {}
+
+func (x *MessageEnvelope) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageEnvelope.ProtoReflect.Descriptor instead.
+func (*MessageEnvelope) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *MessageEnvelope) GetTypeId() int32 {
+ if x != nil {
+ return x.TypeId
+ }
+ return 0
+}
+
+func (x *MessageEnvelope) GetMessageData() []byte {
+ if x != nil {
+ return x.MessageData
+ }
+ return nil
+}
+
+func (x *MessageEnvelope) GetTarget() int32 {
+ if x != nil {
+ return x.Target
+ }
+ return 0
+}
+
+func (x *MessageEnvelope) GetSender() int32 {
+ if x != nil {
+ return x.Sender
+ }
+ return 0
+}
+
+func (x *MessageEnvelope) GetSerializerId() int32 {
+ if x != nil {
+ return x.SerializerId
+ }
+ return 0
+}
+
+func (x *MessageEnvelope) GetMessageHeader() *MessageHeader {
+ if x != nil {
+ return x.MessageHeader
+ }
+ return nil
+}
+
+func (x *MessageEnvelope) GetTargetRequestId() uint32 {
+ if x != nil {
+ return x.TargetRequestId
+ }
+ return 0
+}
+
+func (x *MessageEnvelope) GetSenderRequestId() uint32 {
+ if x != nil {
+ return x.SenderRequestId
+ }
+ return 0
+}
+
+type MessageHeader struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ HeaderData map[string]string `protobuf:"bytes,1,rep,name=header_data,json=headerData,proto3" json:"header_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *MessageHeader) Reset() {
+ *x = MessageHeader{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *MessageHeader) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageHeader) ProtoMessage() {}
+
+func (x *MessageHeader) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageHeader.ProtoReflect.Descriptor instead.
+func (*MessageHeader) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *MessageHeader) GetHeaderData() map[string]string {
+ if x != nil {
+ return x.HeaderData
+ }
+ return nil
+}
+
+type ActorPidRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
+}
+
+func (x *ActorPidRequest) Reset() {
+ *x = ActorPidRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActorPidRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActorPidRequest) ProtoMessage() {}
+
+func (x *ActorPidRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActorPidRequest.ProtoReflect.Descriptor instead.
+func (*ActorPidRequest) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ActorPidRequest) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *ActorPidRequest) GetKind() string {
+ if x != nil {
+ return x.Kind
+ }
+ return ""
+}
+
+type ActorPidResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+ StatusCode int32 `protobuf:"varint,2,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+}
+
+func (x *ActorPidResponse) Reset() {
+ *x = ActorPidResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ActorPidResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActorPidResponse) ProtoMessage() {}
+
+func (x *ActorPidResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[5]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActorPidResponse.ProtoReflect.Descriptor instead.
+func (*ActorPidResponse) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ActorPidResponse) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+func (x *ActorPidResponse) GetStatusCode() int32 {
+ if x != nil {
+ return x.StatusCode
+ }
+ return 0
+}
+
+type ConnectRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Types that are assignable to ConnectionType:
+ // *ConnectRequest_ClientConnection
+ // *ConnectRequest_ServerConnection
+ ConnectionType isConnectRequest_ConnectionType `protobuf_oneof:"connection_type"`
+}
+
+func (x *ConnectRequest) Reset() {
+ *x = ConnectRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ConnectRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConnectRequest) ProtoMessage() {}
+
+func (x *ConnectRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[6]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead.
+func (*ConnectRequest) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{6}
+}
+
+func (m *ConnectRequest) GetConnectionType() isConnectRequest_ConnectionType {
+ if m != nil {
+ return m.ConnectionType
+ }
+ return nil
+}
+
+func (x *ConnectRequest) GetClientConnection() *ClientConnection {
+ if x, ok := x.GetConnectionType().(*ConnectRequest_ClientConnection); ok {
+ return x.ClientConnection
+ }
+ return nil
+}
+
+func (x *ConnectRequest) GetServerConnection() *ServerConnection {
+ if x, ok := x.GetConnectionType().(*ConnectRequest_ServerConnection); ok {
+ return x.ServerConnection
+ }
+ return nil
+}
+
+type isConnectRequest_ConnectionType interface {
+ isConnectRequest_ConnectionType()
+}
+
+type ConnectRequest_ClientConnection struct {
+ ClientConnection *ClientConnection `protobuf:"bytes,1,opt,name=client_connection,json=clientConnection,proto3,oneof"`
+}
+
+type ConnectRequest_ServerConnection struct {
+ ServerConnection *ServerConnection `protobuf:"bytes,2,opt,name=server_connection,json=serverConnection,proto3,oneof"`
+}
+
+func (*ConnectRequest_ClientConnection) isConnectRequest_ConnectionType() {}
+
+func (*ConnectRequest_ServerConnection) isConnectRequest_ConnectionType() {}
+
+type DisconnectRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *DisconnectRequest) Reset() {
+ *x = DisconnectRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *DisconnectRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DisconnectRequest) ProtoMessage() {}
+
+func (x *DisconnectRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[7]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use DisconnectRequest.ProtoReflect.Descriptor instead.
+func (*DisconnectRequest) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{7}
+}
+
+type ClientConnection struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SystemId string `protobuf:"bytes,1,opt,name=SystemId,proto3" json:"SystemId,omitempty"`
+}
+
+func (x *ClientConnection) Reset() {
+ *x = ClientConnection{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ClientConnection) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientConnection) ProtoMessage() {}
+
+func (x *ClientConnection) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[8]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClientConnection.ProtoReflect.Descriptor instead.
+func (*ClientConnection) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *ClientConnection) GetSystemId() string {
+ if x != nil {
+ return x.SystemId
+ }
+ return ""
+}
+
+type ServerConnection struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ SystemId string `protobuf:"bytes,1,opt,name=SystemId,proto3" json:"SystemId,omitempty"`
+ Address string `protobuf:"bytes,2,opt,name=Address,proto3" json:"Address,omitempty"`
+}
+
+func (x *ServerConnection) Reset() {
+ *x = ServerConnection{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ServerConnection) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ServerConnection) ProtoMessage() {}
+
+func (x *ServerConnection) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[9]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ServerConnection.ProtoReflect.Descriptor instead.
+func (*ServerConnection) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *ServerConnection) GetSystemId() string {
+ if x != nil {
+ return x.SystemId
+ }
+ return ""
+}
+
+func (x *ServerConnection) GetAddress() string {
+ if x != nil {
+ return x.Address
+ }
+ return ""
+}
+
+type ConnectResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ MemberId string `protobuf:"bytes,2,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"`
+ Blocked bool `protobuf:"varint,3,opt,name=blocked,proto3" json:"blocked,omitempty"`
+}
+
+func (x *ConnectResponse) Reset() {
+ *x = ConnectResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ConnectResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConnectResponse) ProtoMessage() {}
+
+func (x *ConnectResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[10]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConnectResponse.ProtoReflect.Descriptor instead.
+func (*ConnectResponse) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *ConnectResponse) GetMemberId() string {
+ if x != nil {
+ return x.MemberId
+ }
+ return ""
+}
+
+func (x *ConnectResponse) GetBlocked() bool {
+ if x != nil {
+ return x.Blocked
+ }
+ return false
+}
+
+type ListProcessesRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"`
+ Type ListProcessesMatchType `protobuf:"varint,2,opt,name=type,proto3,enum=remote.ListProcessesMatchType" json:"type,omitempty"`
+}
+
+func (x *ListProcessesRequest) Reset() {
+ *x = ListProcessesRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[11]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListProcessesRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListProcessesRequest) ProtoMessage() {}
+
+func (x *ListProcessesRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[11]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListProcessesRequest.ProtoReflect.Descriptor instead.
+func (*ListProcessesRequest) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{11}
+}
+
+func (x *ListProcessesRequest) GetPattern() string {
+ if x != nil {
+ return x.Pattern
+ }
+ return ""
+}
+
+func (x *ListProcessesRequest) GetType() ListProcessesMatchType {
+ if x != nil {
+ return x.Type
+ }
+ return ListProcessesMatchType_MatchPartOfString
+}
+
+type ListProcessesResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pids []*actor.PID `protobuf:"bytes,1,rep,name=pids,proto3" json:"pids,omitempty"`
+}
+
+func (x *ListProcessesResponse) Reset() {
+ *x = ListProcessesResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[12]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *ListProcessesResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListProcessesResponse) ProtoMessage() {}
+
+func (x *ListProcessesResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[12]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListProcessesResponse.ProtoReflect.Descriptor instead.
+func (*ListProcessesResponse) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *ListProcessesResponse) GetPids() []*actor.PID {
+ if x != nil {
+ return x.Pids
+ }
+ return nil
+}
+
+type GetProcessDiagnosticsRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Pid *actor.PID `protobuf:"bytes,1,opt,name=pid,proto3" json:"pid,omitempty"`
+}
+
+func (x *GetProcessDiagnosticsRequest) Reset() {
+ *x = GetProcessDiagnosticsRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[13]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetProcessDiagnosticsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetProcessDiagnosticsRequest) ProtoMessage() {}
+
+func (x *GetProcessDiagnosticsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[13]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetProcessDiagnosticsRequest.ProtoReflect.Descriptor instead.
+func (*GetProcessDiagnosticsRequest) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{13}
+}
+
+func (x *GetProcessDiagnosticsRequest) GetPid() *actor.PID {
+ if x != nil {
+ return x.Pid
+ }
+ return nil
+}
+
+type GetProcessDiagnosticsResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ DiagnosticsString string `protobuf:"bytes,1,opt,name=diagnostics_string,json=diagnosticsString,proto3" json:"diagnostics_string,omitempty"`
+}
+
+func (x *GetProcessDiagnosticsResponse) Reset() {
+ *x = GetProcessDiagnosticsResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_remote_proto_msgTypes[14]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetProcessDiagnosticsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetProcessDiagnosticsResponse) ProtoMessage() {}
+
+func (x *GetProcessDiagnosticsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_remote_proto_msgTypes[14]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetProcessDiagnosticsResponse.ProtoReflect.Descriptor instead.
+func (*GetProcessDiagnosticsResponse) Descriptor() ([]byte, []int) {
+ return file_remote_proto_rawDescGZIP(), []int{14}
+}
+
+func (x *GetProcessDiagnosticsResponse) GetDiagnosticsString() string {
+ if x != nil {
+ return x.DiagnosticsString
+ }
+ return ""
+}
+
+var File_remote_proto protoreflect.FileDescriptor
+
+var file_remote_proto_rawDesc = []byte{
+ 0x0a, 0x0c, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,
+ 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x1a, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65,
+ 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x3b, 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x61, 0x74,
+ 0x63, 0x68, 0x48, 0x00, 0x52, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x61, 0x74,
+ 0x63, 0x68, 0x12, 0x41, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65,
+ 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
+ 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+ 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x6e,
+ 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x64,
+ 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+ 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+ 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61,
+ 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xb0, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x73, 0x73,
+ 0x61, 0x67, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x79, 0x70, 0x65,
+ 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x74, 0x79,
+ 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65,
+ 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72,
+ 0x2e, 0x50, 0x49, 0x44, 0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x35, 0x0a,
+ 0x09, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
+ 0x32, 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+ 0x65, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x09, 0x65, 0x6e, 0x76, 0x65, 0x6c,
+ 0x6f, 0x70, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18,
+ 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49,
+ 0x44, 0x52, 0x07, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x22, 0xb8, 0x02, 0x0a, 0x0f, 0x4d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x17,
+ 0x0a, 0x07, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
+ 0x06, 0x74, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61,
+ 0x67, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d,
+ 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61,
+ 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67,
+ 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01,
+ 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65,
+ 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28,
+ 0x05, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x49, 0x64, 0x12,
+ 0x3c, 0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65,
+ 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+ 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0d,
+ 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a,
+ 0x11, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f,
+ 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, 0x6e,
+ 0x64, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08,
+ 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
+ 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+ 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65,
+ 0x72, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x65, 0x61,
+ 0x64, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e,
+ 0x74, 0x72, 0x79, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x1a,
+ 0x3d, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74,
+ 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x39,
+ 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x6f, 0x72, 0x50, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x51, 0x0a, 0x10, 0x41, 0x63, 0x74,
+ 0x6f, 0x72, 0x50, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a,
+ 0x03, 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74,
+ 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73,
+ 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
+ 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x22, 0xb5, 0x01, 0x0a,
+ 0x0e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+ 0x47, 0x0a, 0x11, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
+ 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x65, 0x6d,
+ 0x6f, 0x74, 0x65, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
+ 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52,
+ 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f,
+ 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+ 0x74, 0x79, 0x70, 0x65, 0x22, 0x13, 0x0a, 0x11, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
+ 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x10, 0x43, 0x6c, 0x69,
+ 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a,
+ 0x08, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x08, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a,
+ 0x08, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x08, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x64, 0x64,
+ 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72,
+ 0x65, 0x73, 0x73, 0x22, 0x48, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65,
+ 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72,
+ 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x62, 0x65,
+ 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03,
+ 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x64, 0x0a,
+ 0x14, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65,
+ 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12,
+ 0x32, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e,
+ 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65,
+ 0x73, 0x73, 0x65, 0x73, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74,
+ 0x79, 0x70, 0x65, 0x22, 0x37, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65,
+ 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04,
+ 0x70, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74,
+ 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x04, 0x70, 0x69, 0x64, 0x73, 0x22, 0x3c, 0x0a, 0x1c,
+ 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f,
+ 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x03,
+ 0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f,
+ 0x72, 0x2e, 0x50, 0x49, 0x44, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x4e, 0x0a, 0x1d, 0x47, 0x65,
+ 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74,
+ 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x64,
+ 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e,
+ 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73,
+ 0x74, 0x69, 0x63, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2a, 0x55, 0x0a, 0x16, 0x4c, 0x69,
+ 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x4d, 0x61, 0x74, 0x63, 0x68,
+ 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x50, 0x61, 0x72,
+ 0x74, 0x4f, 0x66, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4d,
+ 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x10,
+ 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10,
+ 0x02, 0x32, 0x81, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x3d,
+ 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f,
+ 0x74, 0x65, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+ 0x1a, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
+ 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4e, 0x0a,
+ 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x1c,
+ 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63,
+ 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
+ 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x66, 0x0a,
+ 0x15, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x69, 0x61, 0x67, 0x6e,
+ 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x24, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e,
+ 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f,
+ 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x72,
+ 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+ 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
+ 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2b, 0x5a, 0x29, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x72, 0x65, 0x6d, 0x6f,
+ 0x74, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_remote_proto_rawDescOnce sync.Once
+ file_remote_proto_rawDescData = file_remote_proto_rawDesc
+)
+
+func file_remote_proto_rawDescGZIP() []byte {
+ file_remote_proto_rawDescOnce.Do(func() {
+ file_remote_proto_rawDescData = protoimpl.X.CompressGZIP(file_remote_proto_rawDescData)
+ })
+ return file_remote_proto_rawDescData
+}
+
+var file_remote_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_remote_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
+var file_remote_proto_goTypes = []interface{}{
+ (ListProcessesMatchType)(0), // 0: remote.ListProcessesMatchType
+ (*RemoteMessage)(nil), // 1: remote.RemoteMessage
+ (*MessageBatch)(nil), // 2: remote.MessageBatch
+ (*MessageEnvelope)(nil), // 3: remote.MessageEnvelope
+ (*MessageHeader)(nil), // 4: remote.MessageHeader
+ (*ActorPidRequest)(nil), // 5: remote.ActorPidRequest
+ (*ActorPidResponse)(nil), // 6: remote.ActorPidResponse
+ (*ConnectRequest)(nil), // 7: remote.ConnectRequest
+ (*DisconnectRequest)(nil), // 8: remote.DisconnectRequest
+ (*ClientConnection)(nil), // 9: remote.ClientConnection
+ (*ServerConnection)(nil), // 10: remote.ServerConnection
+ (*ConnectResponse)(nil), // 11: remote.ConnectResponse
+ (*ListProcessesRequest)(nil), // 12: remote.ListProcessesRequest
+ (*ListProcessesResponse)(nil), // 13: remote.ListProcessesResponse
+ (*GetProcessDiagnosticsRequest)(nil), // 14: remote.GetProcessDiagnosticsRequest
+ (*GetProcessDiagnosticsResponse)(nil), // 15: remote.GetProcessDiagnosticsResponse
+ nil, // 16: remote.MessageHeader.HeaderDataEntry
+ (*actor.PID)(nil), // 17: actor.PID
+}
+var file_remote_proto_depIdxs = []int32{
+ 2, // 0: remote.RemoteMessage.message_batch:type_name -> remote.MessageBatch
+ 7, // 1: remote.RemoteMessage.connect_request:type_name -> remote.ConnectRequest
+ 11, // 2: remote.RemoteMessage.connect_response:type_name -> remote.ConnectResponse
+ 8, // 3: remote.RemoteMessage.disconnect_request:type_name -> remote.DisconnectRequest
+ 17, // 4: remote.MessageBatch.targets:type_name -> actor.PID
+ 3, // 5: remote.MessageBatch.envelopes:type_name -> remote.MessageEnvelope
+ 17, // 6: remote.MessageBatch.senders:type_name -> actor.PID
+ 4, // 7: remote.MessageEnvelope.message_header:type_name -> remote.MessageHeader
+ 16, // 8: remote.MessageHeader.header_data:type_name -> remote.MessageHeader.HeaderDataEntry
+ 17, // 9: remote.ActorPidResponse.pid:type_name -> actor.PID
+ 9, // 10: remote.ConnectRequest.client_connection:type_name -> remote.ClientConnection
+ 10, // 11: remote.ConnectRequest.server_connection:type_name -> remote.ServerConnection
+ 0, // 12: remote.ListProcessesRequest.type:type_name -> remote.ListProcessesMatchType
+ 17, // 13: remote.ListProcessesResponse.pids:type_name -> actor.PID
+ 17, // 14: remote.GetProcessDiagnosticsRequest.pid:type_name -> actor.PID
+ 1, // 15: remote.Remoting.Receive:input_type -> remote.RemoteMessage
+ 12, // 16: remote.Remoting.ListProcesses:input_type -> remote.ListProcessesRequest
+ 14, // 17: remote.Remoting.GetProcessDiagnostics:input_type -> remote.GetProcessDiagnosticsRequest
+ 1, // 18: remote.Remoting.Receive:output_type -> remote.RemoteMessage
+ 13, // 19: remote.Remoting.ListProcesses:output_type -> remote.ListProcessesResponse
+ 15, // 20: remote.Remoting.GetProcessDiagnostics:output_type -> remote.GetProcessDiagnosticsResponse
+ 18, // [18:21] is the sub-list for method output_type
+ 15, // [15:18] is the sub-list for method input_type
+ 15, // [15:15] is the sub-list for extension type_name
+ 15, // [15:15] is the sub-list for extension extendee
+ 0, // [0:15] is the sub-list for field type_name
+}
+
+func init() { file_remote_proto_init() }
+func file_remote_proto_init() {
+ if File_remote_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_remote_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoteMessage); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MessageBatch); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MessageEnvelope); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*MessageHeader); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActorPidRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ActorPidResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ConnectRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*DisconnectRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ClientConnection); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ServerConnection); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ConnectResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListProcessesRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*ListProcessesResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetProcessDiagnosticsRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_remote_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetProcessDiagnosticsResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ file_remote_proto_msgTypes[0].OneofWrappers = []interface{}{
+ (*RemoteMessage_MessageBatch)(nil),
+ (*RemoteMessage_ConnectRequest)(nil),
+ (*RemoteMessage_ConnectResponse)(nil),
+ (*RemoteMessage_DisconnectRequest)(nil),
+ }
+ file_remote_proto_msgTypes[6].OneofWrappers = []interface{}{
+ (*ConnectRequest_ClientConnection)(nil),
+ (*ConnectRequest_ServerConnection)(nil),
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_remote_proto_rawDesc,
+ NumEnums: 1,
+ NumMessages: 16,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_remote_proto_goTypes,
+ DependencyIndexes: file_remote_proto_depIdxs,
+ EnumInfos: file_remote_proto_enumTypes,
+ MessageInfos: file_remote_proto_msgTypes,
+ }.Build()
+ File_remote_proto = out.File
+ file_remote_proto_rawDesc = nil
+ file_remote_proto_goTypes = nil
+ file_remote_proto_depIdxs = nil
+}
diff --git a/remote/remote.proto b/remote/remote.proto
new file mode 100644
index 0000000000000000000000000000000000000000..394dcc402bfe2ba7dad29d24d3b7e415542bf05f
--- /dev/null
+++ b/remote/remote.proto
@@ -0,0 +1,100 @@
+syntax = "proto3";
+package remote;
+option go_package = "/gitee.com/simplexyz/simpleactor-go/remote";
+import "actor.proto";
+
+
+message RemoteMessage {
+ oneof message_type {
+ MessageBatch message_batch = 1;
+ ConnectRequest connect_request = 2;
+ ConnectResponse connect_response = 3;
+ DisconnectRequest disconnect_request = 4;
+ }
+}
+
+message MessageBatch {
+ repeated string type_names = 1;
+ repeated actor.PID targets = 2;
+ repeated MessageEnvelope envelopes = 3;
+ repeated actor.PID senders = 4;
+}
+
+message MessageEnvelope {
+ int32 type_id = 1;
+ bytes message_data = 2;
+ int32 target = 3;
+ int32 sender = 4;
+ int32 serializer_id = 5;
+ MessageHeader message_header = 6;
+ uint32 target_request_id = 7;
+ uint32 sender_request_id = 8;
+}
+
+message MessageHeader {
+ map header_data = 1;
+}
+
+message ActorPidRequest {
+ string name = 1;
+ string kind = 2;
+}
+
+message ActorPidResponse {
+ actor.PID pid = 1;
+ int32 status_code = 2;
+}
+
+message ConnectRequest {
+ oneof connection_type {
+ ClientConnection client_connection = 1;
+ ServerConnection server_connection = 2;
+ }
+}
+
+message DisconnectRequest {
+
+}
+
+message ClientConnection {
+ string SystemId = 1;
+}
+
+message ServerConnection {
+ string SystemId = 1;
+ string Address = 2;
+}
+
+message ConnectResponse {
+ string member_id = 2;
+ bool blocked = 3;
+}
+
+service Remoting {
+ rpc Receive (stream RemoteMessage) returns (stream RemoteMessage) {}
+ rpc ListProcesses(ListProcessesRequest) returns (ListProcessesResponse) {}
+ rpc GetProcessDiagnostics(GetProcessDiagnosticsRequest) returns (GetProcessDiagnosticsResponse) {}
+}
+
+message ListProcessesRequest {
+ string pattern = 1;
+ ListProcessesMatchType type = 2;
+}
+
+enum ListProcessesMatchType {
+ MatchPartOfString = 0;
+ MatchExactString = 1;
+ MatchRegex = 2;
+}
+
+message ListProcessesResponse {
+ repeated actor.PID pids = 1;
+}
+
+message GetProcessDiagnosticsRequest {
+ actor.PID pid = 1;
+}
+
+message GetProcessDiagnosticsResponse {
+ string diagnostics_string= 1;
+}
\ No newline at end of file
diff --git a/remote/remote_grpc.pb.go b/remote/remote_grpc.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..ade00fc60c16780c490019e7c57772d363614d2d
--- /dev/null
+++ b/remote/remote_grpc.pb.go
@@ -0,0 +1,210 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc v3.19.1
+// source: remote.proto
+
+package remote
+
+import (
+ context "context"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// RemotingClient is the client API for Remoting service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type RemotingClient interface {
+ Receive(ctx context.Context, opts ...grpc.CallOption) (Remoting_ReceiveClient, error)
+ ListProcesses(ctx context.Context, in *ListProcessesRequest, opts ...grpc.CallOption) (*ListProcessesResponse, error)
+ GetProcessDiagnostics(ctx context.Context, in *GetProcessDiagnosticsRequest, opts ...grpc.CallOption) (*GetProcessDiagnosticsResponse, error)
+}
+
+type remotingClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewRemotingClient(cc grpc.ClientConnInterface) RemotingClient {
+ return &remotingClient{cc}
+}
+
+func (c *remotingClient) Receive(ctx context.Context, opts ...grpc.CallOption) (Remoting_ReceiveClient, error) {
+ stream, err := c.cc.NewStream(ctx, &Remoting_ServiceDesc.Streams[0], "/remote.Remoting/Receive", opts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &remotingReceiveClient{stream}
+ return x, nil
+}
+
+type Remoting_ReceiveClient interface {
+ Send(*RemoteMessage) error
+ Recv() (*RemoteMessage, error)
+ grpc.ClientStream
+}
+
+type remotingReceiveClient struct {
+ grpc.ClientStream
+}
+
+func (x *remotingReceiveClient) Send(m *RemoteMessage) error {
+ return x.ClientStream.SendMsg(m)
+}
+
+func (x *remotingReceiveClient) Recv() (*RemoteMessage, error) {
+ m := new(RemoteMessage)
+ if err := x.ClientStream.RecvMsg(m); err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+func (c *remotingClient) ListProcesses(ctx context.Context, in *ListProcessesRequest, opts ...grpc.CallOption) (*ListProcessesResponse, error) {
+ out := new(ListProcessesResponse)
+ err := c.cc.Invoke(ctx, "/remote.Remoting/ListProcesses", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *remotingClient) GetProcessDiagnostics(ctx context.Context, in *GetProcessDiagnosticsRequest, opts ...grpc.CallOption) (*GetProcessDiagnosticsResponse, error) {
+ out := new(GetProcessDiagnosticsResponse)
+ err := c.cc.Invoke(ctx, "/remote.Remoting/GetProcessDiagnostics", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// RemotingServer is the server API for Remoting service.
+// All implementations must embed UnimplementedRemotingServer
+// for forward compatibility
+type RemotingServer interface {
+ Receive(Remoting_ReceiveServer) error
+ ListProcesses(context.Context, *ListProcessesRequest) (*ListProcessesResponse, error)
+ GetProcessDiagnostics(context.Context, *GetProcessDiagnosticsRequest) (*GetProcessDiagnosticsResponse, error)
+ mustEmbedUnimplementedRemotingServer()
+}
+
+// UnimplementedRemotingServer must be embedded to have forward compatible implementations.
+type UnimplementedRemotingServer struct {
+}
+
+func (UnimplementedRemotingServer) Receive(Remoting_ReceiveServer) error {
+ return status.Errorf(codes.Unimplemented, "method Receive not implemented")
+}
+func (UnimplementedRemotingServer) ListProcesses(context.Context, *ListProcessesRequest) (*ListProcessesResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method ListProcesses not implemented")
+}
+func (UnimplementedRemotingServer) GetProcessDiagnostics(context.Context, *GetProcessDiagnosticsRequest) (*GetProcessDiagnosticsResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method GetProcessDiagnostics not implemented")
+}
+func (UnimplementedRemotingServer) mustEmbedUnimplementedRemotingServer() {}
+
+// UnsafeRemotingServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to RemotingServer will
+// result in compilation errors.
+type UnsafeRemotingServer interface {
+ mustEmbedUnimplementedRemotingServer()
+}
+
+func RegisterRemotingServer(s grpc.ServiceRegistrar, srv RemotingServer) {
+ s.RegisterService(&Remoting_ServiceDesc, srv)
+}
+
+func _Remoting_Receive_Handler(srv interface{}, stream grpc.ServerStream) error {
+ return srv.(RemotingServer).Receive(&remotingReceiveServer{stream})
+}
+
+type Remoting_ReceiveServer interface {
+ Send(*RemoteMessage) error
+ Recv() (*RemoteMessage, error)
+ grpc.ServerStream
+}
+
+type remotingReceiveServer struct {
+ grpc.ServerStream
+}
+
+func (x *remotingReceiveServer) Send(m *RemoteMessage) error {
+ return x.ServerStream.SendMsg(m)
+}
+
+func (x *remotingReceiveServer) Recv() (*RemoteMessage, error) {
+ m := new(RemoteMessage)
+ if err := x.ServerStream.RecvMsg(m); err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+func _Remoting_ListProcesses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ListProcessesRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(RemotingServer).ListProcesses(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/remote.Remoting/ListProcesses",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(RemotingServer).ListProcesses(ctx, req.(*ListProcessesRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _Remoting_GetProcessDiagnostics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(GetProcessDiagnosticsRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(RemotingServer).GetProcessDiagnostics(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/remote.Remoting/GetProcessDiagnostics",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(RemotingServer).GetProcessDiagnostics(ctx, req.(*GetProcessDiagnosticsRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+// Remoting_ServiceDesc is the grpc.ServiceDesc for Remoting service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Remoting_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "remote.Remoting",
+ HandlerType: (*RemotingServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "ListProcesses",
+ Handler: _Remoting_ListProcesses_Handler,
+ },
+ {
+ MethodName: "GetProcessDiagnostics",
+ Handler: _Remoting_GetProcessDiagnostics_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{
+ {
+ StreamName: "Receive",
+ Handler: _Remoting_Receive_Handler,
+ ServerStreams: true,
+ ClientStreams: true,
+ },
+ },
+ Metadata: "remote.proto",
+}
diff --git a/remote/remote_handler.go b/remote/remote_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..a4816e928c4736927244e48a8288bae6f8399925
--- /dev/null
+++ b/remote/remote_handler.go
@@ -0,0 +1,13 @@
+package remote
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+// func remoteHandler(pid *actor.PID) (actor.Process, bool) {
+// ref := newProcess(pid, nil)
+// return ref, true
+// }
+
+func (r *Remote) remoteHandler(pid *actor.PID) (actor.Process, bool) {
+ ref := newProcess(pid, r)
+ return ref, true
+}
diff --git a/remote/remote_process.go b/remote/remote_process.go
new file mode 100644
index 0000000000000000000000000000000000000000..b5f3c5ad787c11642fe0207217268bdda4494199
--- /dev/null
+++ b/remote/remote_process.go
@@ -0,0 +1,50 @@
+package remote
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type process struct {
+ pid *actor.PID
+ remote *Remote
+}
+
+func newProcess(pid *actor.PID, r *Remote) actor.Process {
+ return &process{
+ pid: pid,
+ remote: r,
+ }
+}
+
+var _ actor.Process = &process{}
+
+func (ref *process) SendUserMessage(pid *actor.PID, message interface{}) {
+ header, msg, sender := actor.UnwrapEnvelope(message)
+ ref.remote.SendMessage(pid, header, msg, sender, -1)
+}
+
+func (ref *process) SendSystemMessage(pid *actor.PID, message interface{}) {
+ // intercept any Watch messages and direct them to the endpoint manager
+ switch msg := message.(type) {
+ case *actor.Watch:
+ rw := &remoteWatch{
+ Watcher: msg.Watcher,
+ Watchee: pid,
+ }
+ // endpointManager.remoteWatch(rw)
+ ref.remote.edpManager.remoteWatch(rw)
+ case *actor.Unwatch:
+ ruw := &remoteUnwatch{
+ Watcher: msg.Watcher,
+ Watchee: pid,
+ }
+ // endpointManager.remoteUnwatch(ruw)
+ ref.remote.edpManager.remoteUnwatch(ruw)
+ default:
+ ref.remote.SendMessage(pid, nil, message, nil, -1)
+ }
+}
+
+func (ref *process) Stop(pid *actor.PID) {
+ ref.SendSystemMessage(pid, stopMessage)
+}
diff --git a/remote/response_status_code.go b/remote/response_status_code.go
new file mode 100644
index 0000000000000000000000000000000000000000..89042dd61fe7e9bb6277e9624e2ba32f1a11ced9
--- /dev/null
+++ b/remote/response_status_code.go
@@ -0,0 +1,58 @@
+package remote
+
+import "strconv"
+
+type ResponseStatusCode int32
+
+const (
+ ResponseStatusCodeOK ResponseStatusCode = iota
+ ResponseStatusCodeUNAVAILABLE
+ ResponseStatusCodeTIMEOUT
+ ResponseStatusCodePROCESSNAMEALREADYEXIST
+ ResponseStatusCodeERROR
+ ResponseStatusCodeDeadLetter
+ ResponseStatusCodeMAX // just a boundary.
+)
+
+var responseNames [ResponseStatusCodeMAX]string
+
+func init() {
+ responseNames[ResponseStatusCodeOK] = "ResponseStatusCodeOK"
+ responseNames[ResponseStatusCodeUNAVAILABLE] = "ResponseStatusCodeUNAVAILABLE"
+ responseNames[ResponseStatusCodeTIMEOUT] = "ResponseStatusCodeTIMEOUT"
+ responseNames[ResponseStatusCodePROCESSNAMEALREADYEXIST] = "ResponseStatusCodePROCESSNAMEALREADYEXIST"
+ responseNames[ResponseStatusCodePROCESSNAMEALREADYEXIST] = "ResponseStatusCodePROCESSNAMEALREADYEXIST"
+ responseNames[ResponseStatusCodeERROR] = "ResponseStatusCodeERROR"
+ responseNames[ResponseStatusCodeDeadLetter] = "ResponseStatusCodeDeadLetter"
+}
+
+func (c ResponseStatusCode) ToInt32() int32 {
+ return int32(c)
+}
+
+func (c ResponseStatusCode) String() string {
+ statusCode := int(c)
+ if statusCode < 0 || statusCode >= len(responseNames) {
+ return "ResponseStatusCode-" + strconv.Itoa(int(c))
+ }
+ return responseNames[statusCode]
+}
+
+func (c ResponseStatusCode) AsError() *ResponseError {
+ switch c {
+ case ResponseStatusCodeOK:
+ return nil
+ case ResponseStatusCodeUNAVAILABLE:
+ return ErrUnAvailable
+ case ResponseStatusCodeTIMEOUT:
+ return ErrTimeout
+ case ResponseStatusCodePROCESSNAMEALREADYEXIST:
+ return ErrProcessNameAlreadyExist
+ case ResponseStatusCodeERROR:
+ return ErrUnknownError
+ case ResponseStatusCodeDeadLetter:
+ return ErrDeadLetter
+ default:
+ return &ResponseError{c}
+ }
+}
diff --git a/remote/response_status_code_test.go b/remote/response_status_code_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ea81d445b5aa6cb5b4932efac409d9a4660422a
--- /dev/null
+++ b/remote/response_status_code_test.go
@@ -0,0 +1,27 @@
+package remote
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStatusCode_String(t *testing.T) {
+ assert := assert.New(t)
+ for i := 0; i < int(ResponseStatusCodeMAX); i++ {
+ code := ResponseStatusCode(i)
+ assert.NotEmpty(fmt.Sprintf("%s", code))
+ }
+ s := fmt.Sprintf("%s", ResponseStatusCode(100))
+ assert.Equal(s, "ResponseStatusCode-100")
+}
+
+func TestStatusCode_Error(t *testing.T) {
+ assert := assert.New(t)
+ for i := 0; i < int(ResponseStatusCodeMAX); i++ {
+ var err error = nil
+ err = &ResponseError{ResponseStatusCode(i)}
+ assert.Error(err)
+ }
+}
diff --git a/remote/serializer.go b/remote/serializer.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfab1ab5e5e2a6a618229d7f472ab5ae3501cbfd
--- /dev/null
+++ b/remote/serializer.go
@@ -0,0 +1,45 @@
+package remote
+
+var (
+ DefaultSerializerID int32
+ serializers []Serializer
+)
+
+func init() {
+ RegisterSerializer(newProtoSerializer())
+ RegisterSerializer(newJsonSerializer())
+}
+
+func RegisterSerializer(serializer Serializer) {
+ serializers = append(serializers, serializer)
+}
+
+type Serializer interface {
+ Serialize(msg interface{}) ([]byte, error)
+ Deserialize(typeName string, bytes []byte) (interface{}, error)
+ GetTypeName(msg interface{}) (string, error)
+}
+
+func Serialize(message interface{}, serializerID int32) ([]byte, string, error) {
+ res, err := serializers[serializerID].Serialize(message)
+ typeName, err := serializers[serializerID].GetTypeName(message)
+ return res, typeName, err
+}
+
+func Deserialize(message []byte, typeName string, serializerID int32) (interface{}, error) {
+ return serializers[serializerID].Deserialize(typeName, message)
+}
+
+// RootSerializable is the root level in-process representation of a message
+type RootSerializable interface {
+ // Serialize returns the on-the-wire representation of the message
+ // Message -> IRootSerialized -> ByteString
+ Serialize() RootSerialized
+}
+
+// RootSerialized is the root level on-the-wire representation of a message
+type RootSerialized interface {
+ // Deserialize returns the in-process representation of a message
+ // ByteString -> IRootSerialized -> Message
+ Deserialize() RootSerializable
+}
diff --git a/remote/serializer_test.go b/remote/serializer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9549b16727288aef9d002451a576868b92d27ecb
--- /dev/null
+++ b/remote/serializer_test.go
@@ -0,0 +1,57 @@
+package remote
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+//func TestJsonSerializer_round_trip(t *testing.T) {
+// m := &ActorPidRequest{
+// Kind: "abc",
+// Name: "def",
+// }
+// b, typeName, _ := Serialize(m, 1)
+// res, err := Deserialize(b, typeName, 1)
+//
+// assert.Nil(t, err)
+//
+// var typed = res.(*ActorPidRequest)
+// assert.Equal(t, "remote.ActorPidRequest", typeName)
+// assert.Equal(t, m, typed)
+//}
+//
+//func TestJsonSerializer_Serialize_PID_raw(t *testing.T) {
+// system := actor.NewActorSystem()
+// m, _ := system.Root.SpawnNamed(actor.PropsFromFunc(func(ctx actor.Context) {}), "actorpid")
+// var ser = jsonpb.Marshaler{}
+// res, _ := ser.MarshalToString(m)
+// assert.Equal(t, "{\"Address\":\"nonhost\",\"Id\":\"actorpid\"}", res)
+//}
+//
+//func TestJsonSerializer_Serialize_PID(t *testing.T) {
+// system := actor.NewActorSystem()
+// m := system.NewLocalPID("foo")
+// b, typeName, _ := Serialize(m, 1)
+// res, err := Deserialize(b, typeName, 1)
+//
+// assert.Nil(t, err)
+//
+// var typed = res.(*actor.PID)
+// assert.Equal(t, "actor.PID", typeName)
+// assert.Equal(t, m, typed)
+//}
+
+func TestProtobufSerializer_Serialize_PID(t *testing.T) {
+ system := actor.NewActorSystem()
+ m := system.NewLocalPID("foo")
+ b, typeName, _ := Serialize(m, 0)
+ res, err := Deserialize(b, typeName, 0)
+
+ assert.Nil(t, err)
+
+ typed := res.(*actor.PID)
+ assert.Equal(t, "actor.PID", typeName)
+ assert.True(t, m.Equal(typed))
+}
diff --git a/remote/server.go b/remote/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..f3b8258246ea5dd972ba58d59d4bcde9d12218b1
--- /dev/null
+++ b/remote/server.go
@@ -0,0 +1,125 @@
+package remote
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/extensions"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/log"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/grpclog"
+)
+
+var extensionId = extensions.NextExtensionID()
+
+type Remote struct {
+ actorSystem *actor.ActorSystem
+ s *grpc.Server
+ edpReader *endpointReader
+ edpManager *endpointManager
+ config *Config
+ kinds map[string]*actor.Props
+ activatorPid *actor.PID
+ blocklist *BlockList
+}
+
+func NewRemote(actorSystem *actor.ActorSystem, config *Config) *Remote {
+ r := &Remote{
+ actorSystem: actorSystem,
+ config: config,
+ kinds: make(map[string]*actor.Props),
+ blocklist: NewBlockList(),
+ }
+ for k, v := range config.Kinds {
+ r.kinds[k] = v
+ }
+
+ actorSystem.Extensions.Register(r)
+
+ return r
+}
+
+//goland:noinspection GoUnusedExportedFunction
+func GetRemote(actorSystem *actor.ActorSystem) *Remote {
+ r := actorSystem.Extensions.Get(extensionId)
+
+ return r.(*Remote)
+}
+
+func (r *Remote) ExtensionID() extensions.ExtensionID {
+ return extensionId
+}
+
+func (r *Remote) BlockList() *BlockList { return r.blocklist }
+
+// Start the remote server
+func (r *Remote) Start() {
+ grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
+ lis, err := net.Listen("tcp", r.config.Address())
+ if err != nil {
+ panic(fmt.Errorf("failed to listen: %v", err))
+ }
+
+ var address string
+ if r.config.AdvertisedHost != "" {
+ address = r.config.AdvertisedHost
+ } else {
+ address = lis.Addr().String()
+ }
+
+ r.actorSystem.ProcessRegistry.RegisterAddressResolver(r.remoteHandler)
+ r.actorSystem.ProcessRegistry.Address = address
+ plog.Info("Starting remote with address", log.String("address", address))
+
+ r.edpManager = newEndpointManager(r)
+ r.edpManager.start()
+
+ r.s = grpc.NewServer(r.config.ServerOptions...)
+ r.edpReader = newEndpointReader(r)
+ RegisterRemotingServer(r.s, r.edpReader)
+ plog.Info("Starting Proto.Actor server", log.String("address", address))
+ go r.s.Serve(lis)
+}
+
+func (r *Remote) Shutdown(graceful bool) {
+ if graceful {
+ // TODO: need more graceful
+ r.edpReader.suspend(true)
+ r.edpManager.stop()
+
+ // For some reason GRPC doesn't want to stop
+ // Setup timeout as workaround but need to figure out in the future.
+ // TODO: grpc not stopping
+ c := make(chan bool, 1)
+ go func() {
+ r.s.GracefulStop()
+ c <- true
+ }()
+
+ select {
+ case <-c:
+ plog.Info("Stopped Proto.Actor server")
+ case <-time.After(time.Second * 10):
+ r.s.Stop()
+ plog.Info("Stopped Proto.Actor server", log.String("err", "timeout"))
+ }
+ } else {
+ r.s.Stop()
+ plog.Info("Killed Proto.Actor server")
+ }
+}
+
+func (r *Remote) SendMessage(pid *actor.PID, header actor.ReadonlyMessageHeader, message interface{}, sender *actor.PID, serializerID int32) {
+ rd := &remoteDeliver{
+ header: header,
+ message: message,
+ sender: sender,
+ target: pid,
+ serializerID: serializerID,
+ }
+ r.edpManager.remoteDeliver(rd)
+}
diff --git a/remote/server_test.go b/remote/server_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..529a508193e36dd2c9ccb95a0e7b6161c42880ba
--- /dev/null
+++ b/remote/server_test.go
@@ -0,0 +1,224 @@
+package remote
+
+import (
+ "sort"
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStart(t *testing.T) {
+ system := actor.NewActorSystem()
+ config := Configure("localhost", 0)
+ remote := NewRemote(system, config)
+ remote.Start()
+ remote.Shutdown(true)
+}
+
+func TestConfig_WithAdvertisedHost(t *testing.T) {
+ system := actor.NewActorSystem()
+ config := Configure("localhost", 0, WithAdvertisedHost("Banana"))
+ remote := NewRemote(system, config)
+ remote.Start()
+ assert.Equal(t, "Banana", system.Address())
+ remote.Shutdown(true)
+}
+
+func TestRemote_Register(t *testing.T) {
+ system := actor.NewActorSystem()
+ config := Configure("localhost", 0, WithKinds(
+ NewKind("someKind", actor.PropsFromProducer(nil)),
+ NewKind("someOther", actor.PropsFromProducer(nil)),
+ ))
+ remote := NewRemote(system, config)
+
+ kinds := remote.GetKnownKinds()
+ assert.Equal(t, 2, len(kinds))
+ sort.Strings(kinds)
+ assert.Equal(t, "someKind", kinds[0])
+ assert.Equal(t, "someOther", kinds[1])
+}
+
+func TestRemote_RegisterViaOptions(t *testing.T) {
+ system := actor.NewActorSystem()
+ config := Configure("localhost", 0,
+ WithKinds(
+ NewKind("someKind", actor.PropsFromProducer(nil)),
+ NewKind("someOther", actor.PropsFromProducer(nil))))
+
+ remote := NewRemote(system, config)
+ kinds := remote.GetKnownKinds()
+ assert.Equal(t, 2, len(kinds))
+ sort.Strings(kinds)
+ assert.Equal(t, "someKind", kinds[0])
+ assert.Equal(t, "someOther", kinds[1])
+}
+
+func TestRemote_RegisterViaStruct(t *testing.T) {
+ system := actor.NewActorSystem()
+ config := &Config{
+ Host: "localhost",
+ Port: 0,
+ Kinds: map[string]*actor.Props{
+ "someKind": actor.PropsFromProducer(nil),
+ "someOther": actor.PropsFromProducer(nil),
+ },
+ }
+
+ remote := NewRemote(system, config)
+ kinds := remote.GetKnownKinds()
+ assert.Equal(t, 2, len(kinds))
+ sort.Strings(kinds)
+ assert.Equal(t, "someKind", kinds[0])
+ assert.Equal(t, "someOther", kinds[1])
+}
+
+//
+//func (suite *ServerTestSuite) TestStart_AdvertisedAddress() {
+// // Find available Port
+// lis, err := net.Listen("tcp", "127.0.0.1:0") // use :0 to choose available Port
+// if err != nil {
+// panic(err)
+// }
+// //address := lis.Addr()
+// _ = lis.Close()
+//
+// AdvertisedHost := "192.0.2.1:1234"
+// remote.StartMember()
+//
+// suite.NotEmpty(system.ProcessRegistry.RemoteHandlers, "AddressResolver should be registered on server start")
+// suite.Equal(AdvertisedHost, system.ProcessRegistry.Address, "WithAdvertisedHost should have higher priority")
+// suite.NotNil(activatorPid, "Activator actor should be initialized on server start")
+// suite.NotNil(endpointManager, "EndpointManager should be initialized on server start")
+// suite.Equal(AdvertisedHost, remote.config.AdvertisedHost, "Passed configuration option should be used")
+// suite.NotNil(remote.edpReader, "EndpointReader should be initialized on server start")
+// suite.NotNil(remote.s, "gRPC server should be started on server start")
+//}
+//
+//func (suite *ServerTestSuite) TestShutdown_Graceful() {
+// remote.edpReader = &endpointReader{}
+// suite.False(remote.edpReader.suspended, "EndpointReader should not be suspended at beginning")
+//
+// endpointSupervisor, endpointSupervisorProcess := spawnMockProcess("EndpointSupervisor")
+// defer removeMockProcess(endpointSupervisor)
+// endpointSupervisorProcess.On("SendSystemMessage", mock.Anything, mock.Anything).
+// Run(func(args mock.Arguments) {
+// if suite.IsType(&actor.PID{}, args.Get(0)) {
+// pid := args.Get(0).(*actor.PID)
+// suite.Equal(endpointSupervisor, pid)
+// }
+// if suite.IsType(&actor.Watch{}, args.Get(1)) {
+// watch := args.Get(1).(*actor.Watch)
+// system.Root.Send(watch.Watcher, &actor.Terminated{
+// Who: endpointSupervisor,
+// AddressTerminated: false,
+// })
+// }
+// }).
+// Once()
+// endpointSupervisorProcess.On("Stop", endpointSupervisor).Once()
+//
+// endpointManager = &endpointManagerValue{
+// connections: &sync.Map{},
+// remote: remote,
+// endpointSupervisor: endpointSupervisor,
+// endpointSub: system.EventStream.Subscribe(func(evt interface{}) {}),
+// }
+//
+// var activatorProcess *mockProcess
+// activatorPid, activatorProcess = spawnMockProcess("activator")
+// defer removeMockProcess(activatorPid)
+// activatorProcess.On("SendSystemMessage", mock.Anything, mock.Anything).
+// Run(func(args mock.Arguments) {
+// if suite.IsType(&actor.PID{}, args.Get(0)) {
+// pid := args.Get(0).(*actor.PID)
+// suite.Equal(activatorPid, pid)
+// }
+// if suite.IsType(&actor.Watch{}, args.Get(1)) {
+// watch := args.Get(1).(*actor.Watch)
+// system.Root.Send(watch.Watcher, &actor.Terminated{
+// Who: activatorPid,
+// AddressTerminated: false,
+// })
+// }
+// }).
+// Once()
+// activatorProcess.On("Stop", activatorPid).Once()
+//
+// lis, err := net.Listen("tcp", "127.0.0.1:0") // use :0 to choose available Port
+// if err != nil {
+// panic(err)
+// }
+// defer lis.Close()
+//
+// grpcStopped := make(chan struct{}, 1)
+// remote.s = grpc.NewServer()
+// go func() {
+// remote.s.Serve(lis)
+// grpcStopped <- struct{}{}
+// }()
+//
+// remote.Shutdown(true)
+//
+// suite.Nil(endpointManager.endpointSub, "Subscription should reset on shutdown")
+// suite.Nil(endpointManager.connections, "Connections should reset on shutdown")
+//
+// select {
+// case <-time.NewTimer(15 * time.Second).C:
+// suite.FailNow("gRPC server did not stop")
+// case <-grpcStopped:
+// // O.K.
+// }
+//
+// endpointSupervisorProcess.AssertExpectations(suite.T())
+//}
+//
+//func (suite *ServerTestSuite) TestShutdown() {
+// remote.edpReader = &endpointReader{}
+// suite.False(remote.edpReader.suspended, "EndpointReader should not be suspended at beginning")
+//
+// endpointSupervisor, endpointSupervisorProcess := spawnMockProcess("EndpointSupervisor")
+// defer removeMockProcess(endpointSupervisor)
+//
+// var activatorProcess *mockProcess
+// activatorPid, activatorProcess = spawnMockProcess("activator")
+// defer removeMockProcess(activatorPid)
+//
+// endpointManager = &endpointManagerValue{
+// connections: &sync.Map{},
+// remote: nil,
+// endpointSupervisor: endpointSupervisor,
+// endpointSub: system.EventStream.Subscribe(func(evt interface{}) {}),
+// }
+//
+// lis, err := net.Listen("tcp", "127.0.0.1:0") // use :0 to choose available Port
+// if err != nil {
+// panic(err)
+// }
+// defer lis.Close()
+//
+// grpcStopped := make(chan struct{}, 1)
+// remote.s = grpc.NewServer()
+// go func() {
+// remote.s.Serve(lis)
+// grpcStopped <- struct{}{}
+// }()
+//
+// remote.Shutdown(false)
+//
+// suite.NotNil(endpointManager.endpointSub, "Subscription should not reset on non-graceful shutdown")
+// suite.NotNil(endpointManager.connections, "Connections should not reset on non-graceful shutdown")
+//
+// select {
+// case <-time.NewTimer(1 * time.Second).C:
+// suite.FailNow("gRPC server did not stop")
+// case <-grpcStopped:
+// // O.K.
+// }
+//
+// activatorProcess.AssertNotCalled(suite.T(), "SendSystemMessage", mock.Anything, mock.Anything)
+// activatorProcess.AssertExpectations(suite.T())
+// endpointSupervisorProcess.AssertNotCalled(suite.T(), "SendSystemMessage", mock.Anything, mock.Anything)
+// endpointSupervisorProcess.AssertExpectations(suite.T())
+//}
diff --git a/resources/batman.jpg b/resources/batman.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1fbe6602d93fe2de5629b937a87c7c8b3932339c
Binary files /dev/null and b/resources/batman.jpg differ
diff --git a/router/broadcast_router.go b/router/broadcast_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..38009446c40be457ea5ba592550f110c216b4c22
--- /dev/null
+++ b/router/broadcast_router.go
@@ -0,0 +1,54 @@
+package router
+
+import (
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type broadcastGroupRouter struct {
+ GroupRouter
+}
+
+type broadcastPoolRouter struct {
+ PoolRouter
+}
+
+type broadcastRouterState struct {
+ routees *actor.PIDSet
+ sender actor.SenderContext
+}
+
+func (state *broadcastRouterState) SetSender(sender actor.SenderContext) {
+ state.sender = sender
+}
+
+func (state *broadcastRouterState) SetRoutees(routees *actor.PIDSet) {
+ state.routees = routees
+}
+
+func (state *broadcastRouterState) GetRoutees() *actor.PIDSet {
+ return state.routees
+}
+
+func (state *broadcastRouterState) RouteMessage(message interface{}) {
+ state.routees.ForEach(func(i int, pid *actor.PID) {
+ state.sender.Send(pid, message)
+ })
+}
+
+func NewBroadcastPool(size int, opts ...actor.PropsOption) *actor.Props {
+ return (&actor.Props{}).
+ Configure(actor.WithSpawnFunc(spawner(&broadcastPoolRouter{PoolRouter{PoolSize: size}}))).
+ Configure(opts...)
+}
+
+func NewBroadcastGroup(routees ...*actor.PID) *actor.Props {
+ return (&actor.Props{}).Configure(actor.WithSpawnFunc(spawner(&broadcastGroupRouter{GroupRouter{Routees: actor.NewPIDSet(routees...)}})))
+}
+
+func (config *broadcastPoolRouter) CreateRouterState() State {
+ return &broadcastRouterState{}
+}
+
+func (config *broadcastGroupRouter) CreateRouterState() State {
+ return &broadcastRouterState{}
+}
diff --git a/router/broadcast_router_test.go b/router/broadcast_router_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a7068885b4bb4804548802707caa62bf5b3b0c76
--- /dev/null
+++ b/router/broadcast_router_test.go
@@ -0,0 +1,40 @@
+package router
+
+import (
+ "strconv"
+ "sync"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+var system = actor.NewActorSystem()
+
+func TestBroadcastRouterThreadSafe(t *testing.T) {
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+
+ props := actor.PropsFromFunc(func(c actor.Context) {})
+
+ grp := system.Root.Spawn(NewBroadcastGroup())
+ go func() {
+ count := 100
+ for i := 0; i < count; i++ {
+ pid, _ := system.Root.SpawnNamed(props, strconv.Itoa(i))
+ system.Root.Send(grp, &AddRoutee{PID: pid})
+ time.Sleep(10 * time.Millisecond)
+ }
+ wg.Done()
+ }()
+ go func() {
+ count := 100
+ for c := 0; c < count; c++ {
+ system.Root.Send(grp, struct{}{})
+ time.Sleep(10 * time.Millisecond)
+ }
+ wg.Done()
+ }()
+
+ wg.Wait()
+}
diff --git a/router/build.sh b/router/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..4d3f33b53f8514460a573f85351662ed9f21a673
--- /dev/null
+++ b/router/build.sh
@@ -0,0 +1,2 @@
+protoc -I="../actor" --go_out=. --go_opt=paths=source_relative --proto_path=. routercontracts.proto
+
diff --git a/router/common_test.go b/router/common_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f6ef88f5c7c1f6c3da6ae661ea383025ae0b7bdb
--- /dev/null
+++ b/router/common_test.go
@@ -0,0 +1,235 @@
+package router
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/ctxext"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/mock"
+)
+
+var nilPID *actor.PID
+
+func init() {
+ // discard all logging in tests
+ log.SetOutput(ioutil.Discard)
+}
+
+// mockContext
+type mockContext struct {
+ mock.Mock
+}
+
+//
+// Interface: Context
+//
+
+func (m *mockContext) Get(id ctxext.ContextExtensionID) ctxext.ContextExtension {
+ args := m.Called(id)
+ return args.Get(0).(ctxext.ContextExtension)
+}
+
+func (m *mockContext) Set(ext ctxext.ContextExtension) {
+ m.Called(ext)
+}
+
+func (m *mockContext) ActorSystem() *actor.ActorSystem {
+ args := m.Called()
+ return args.Get(0).(*actor.ActorSystem)
+}
+
+func (m *mockContext) Parent() *actor.PID {
+ args := m.Called()
+ return args.Get(0).(*actor.PID)
+}
+
+func (m *mockContext) Self() *actor.PID {
+ args := m.Called()
+ return args.Get(0).(*actor.PID)
+}
+
+func (m *mockContext) Sender() *actor.PID {
+ args := m.Called()
+ return args.Get(0).(*actor.PID)
+}
+
+func (m *mockContext) Actor() actor.Actor {
+ args := m.Called()
+ return args.Get(0).(actor.Actor)
+}
+
+func (m *mockContext) ReceiveTimeout() time.Duration {
+ args := m.Called()
+ return args.Get(0).(time.Duration)
+}
+
+func (m *mockContext) Children() []*actor.PID {
+ args := m.Called()
+ return args.Get(0).([]*actor.PID)
+}
+
+func (m *mockContext) Respond(response interface{}) {
+ m.Called(response)
+}
+
+func (m *mockContext) Stash() {
+ m.Called()
+}
+
+func (m *mockContext) Watch(pid *actor.PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) Unwatch(pid *actor.PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) SetReceiveTimeout(d time.Duration) {
+ m.Called(d)
+}
+
+func (m *mockContext) CancelReceiveTimeout() {
+ m.Called()
+}
+
+func (m *mockContext) Forward(pid *actor.PID) {
+ m.Called()
+}
+
+func (m *mockContext) ReenterAfter(f *actor.Future, cont func(res interface{}, err error)) {
+ m.Called(f, cont)
+}
+
+//
+// Interface: SenderContext
+//
+
+func (m *mockContext) Message() interface{} {
+ args := m.Called()
+ return args.Get(0)
+}
+
+func (m *mockContext) MessageHeader() actor.ReadonlyMessageHeader {
+ args := m.Called()
+ return args.Get(0).(actor.ReadonlyMessageHeader)
+}
+
+func (m *mockContext) Send(pid *actor.PID, message interface{}) {
+ m.Called()
+ p, _ := system.ProcessRegistry.Get(pid)
+ p.SendUserMessage(pid, message)
+}
+
+func (m *mockContext) Request(pid *actor.PID, message interface{}) {
+ args := m.Called()
+ p, _ := system.ProcessRegistry.Get(pid)
+ env := &actor.MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: args.Get(0).(*actor.PID),
+ }
+ p.SendUserMessage(pid, env)
+}
+
+func (m *mockContext) RequestWithCustomSender(pid *actor.PID, message interface{}, sender *actor.PID) {
+ m.Called()
+ p, _ := system.ProcessRegistry.Get(pid)
+ env := &actor.MessageEnvelope{
+ Header: nil,
+ Message: message,
+ Sender: sender,
+ }
+ p.SendUserMessage(pid, env)
+}
+
+func (m *mockContext) RequestFuture(pid *actor.PID, message interface{}, timeout time.Duration) *actor.Future {
+ args := m.Called()
+ m.Called()
+ p, _ := system.ProcessRegistry.Get(pid)
+ p.SendUserMessage(pid, message)
+ return args.Get(0).(*actor.Future)
+}
+
+//
+// Interface: ReceiverContext
+//
+
+func (m *mockContext) Receive(envelope *actor.MessageEnvelope) {
+ m.Called(envelope)
+}
+
+//
+// Interface: SpawnerContext
+//
+
+func (m *mockContext) Spawn(p *actor.Props) *actor.PID {
+ args := m.Called(p)
+ return args.Get(0).(*actor.PID)
+}
+
+func (m *mockContext) SpawnPrefix(p *actor.Props, prefix string) *actor.PID {
+ args := m.Called(p, prefix)
+ return args.Get(0).(*actor.PID)
+}
+
+func (m *mockContext) SpawnNamed(p *actor.Props, name string) (*actor.PID, error) {
+ args := m.Called(p, name)
+ return args.Get(0).(*actor.PID), args.Get(1).(error)
+}
+
+//
+// Interface: StopperContext
+//
+
+func (m *mockContext) Stop(pid *actor.PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) StopFuture(pid *actor.PID) *actor.Future {
+ args := m.Called(pid)
+ return args.Get(0).(*actor.Future)
+}
+
+func (m *mockContext) Poison(pid *actor.PID) {
+ m.Called(pid)
+}
+
+func (m *mockContext) PoisonFuture(pid *actor.PID) *actor.Future {
+ args := m.Called(pid)
+ return args.Get(0).(*actor.Future)
+}
+
+// mockProcess
+type mockProcess struct {
+ mock.Mock
+}
+
+func spawnMockProcess(name string) (*actor.PID, *mockProcess) {
+ p := &mockProcess{}
+ pid, ok := system.ProcessRegistry.Add(p, name)
+ if !ok {
+ panic(fmt.Errorf("did not spawn named process '%s'", name))
+ }
+
+ return pid, p
+}
+
+func removeMockProcess(pid *actor.PID) {
+ system.ProcessRegistry.Remove(pid)
+}
+
+func (m *mockProcess) SendUserMessage(pid *actor.PID, message interface{}) {
+ m.Called(pid, message)
+}
+
+func (m *mockProcess) SendSystemMessage(pid *actor.PID, message interface{}) {
+ m.Called(pid, message)
+}
+
+func (m *mockProcess) Stop(pid *actor.PID) {
+ m.Called(pid)
+}
diff --git a/router/config.go b/router/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..f1d51f3562c03dee3942e81f3a750fc05c825083
--- /dev/null
+++ b/router/config.go
@@ -0,0 +1,102 @@
+package router
+
+import (
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type RouterType int
+
+const (
+ GroupRouterType RouterType = iota
+ PoolRouterType
+)
+
+type RouterConfig interface {
+ RouterType() RouterType
+ OnStarted(context actor.Context, props *actor.Props, state State)
+ CreateRouterState() State
+}
+
+type GroupRouter struct {
+ Routees *actor.PIDSet
+}
+
+type PoolRouter struct {
+ PoolSize int
+}
+
+func (config *GroupRouter) OnStarted(context actor.Context, props *actor.Props, state State) {
+ config.Routees.ForEach(func(i int, pid *actor.PID) {
+ context.Watch(pid)
+ })
+ state.SetSender(context)
+ state.SetRoutees(config.Routees)
+}
+
+func (config *GroupRouter) RouterType() RouterType {
+ return GroupRouterType
+}
+
+func (config *PoolRouter) OnStarted(context actor.Context, props *actor.Props, state State) {
+ var routees actor.PIDSet
+ for i := 0; i < config.PoolSize; i++ {
+ routees.Add(context.Spawn(props))
+ }
+ state.SetSender(context)
+ state.SetRoutees(&routees)
+}
+
+func (config *PoolRouter) RouterType() RouterType {
+ return PoolRouterType
+}
+
+func spawner(config RouterConfig) actor.SpawnFunc {
+ return func(actorSystem *actor.ActorSystem, id string, props *actor.Props, parentContext actor.SpawnerContext) (*actor.PID, error) {
+ return spawn(actorSystem, id, config, props, parentContext)
+ }
+}
+
+func spawn(actorSystem *actor.ActorSystem, id string, config RouterConfig, props *actor.Props, parentContext actor.SpawnerContext) (*actor.PID, error) {
+ ref := &process{
+ actorSystem: actorSystem,
+ }
+ proxy, absent := actorSystem.ProcessRegistry.Add(ref, id)
+ if !absent {
+ return proxy, actor.ErrNameExists
+ }
+
+ pc := *props
+ pc.Configure(actor.WithSpawnFunc(nil))
+ ref.state = config.CreateRouterState()
+
+ if config.RouterType() == GroupRouterType {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ ref.router, _ = actor.DefaultSpawner(actorSystem, id+"/router", actor.PropsFromProducer(func() actor.Actor {
+ return &groupRouterActor{
+ props: &pc,
+ config: config,
+ state: ref.state,
+ wg: wg,
+ }
+ }), parentContext)
+ wg.Wait() // wait for routerActor to start
+ } else {
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ ref.router, _ = actor.DefaultSpawner(actorSystem, id+"/router", actor.PropsFromProducer(func() actor.Actor {
+ return &poolRouterActor{
+ props: &pc,
+ config: config,
+ state: ref.state,
+ wg: wg,
+ }
+ }), parentContext)
+ wg.Wait() // wait for routerActor to start
+ }
+
+ ref.parent = parentContext.Self()
+ return proxy, nil
+}
diff --git a/router/config_test.go b/router/config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..0547681531e7e7c2672ec6b59a1bf18f7816d60b
--- /dev/null
+++ b/router/config_test.go
@@ -0,0 +1,23 @@
+package router
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSpawn(t *testing.T) {
+ pr := &broadcastPoolRouter{PoolRouter{PoolSize: 1}}
+ pid, err := spawn(system, "foo", pr, actor.PropsFromFunc(func(context actor.Context) {}), system.Root)
+ assert.NoError(t, err)
+
+ _, exists := system.ProcessRegistry.Get(system.NewLocalPID("foo/router"))
+ assert.True(t, exists)
+
+ err = system.Root.StopFuture(pid).Wait()
+ assert.NoError(t, err)
+
+ _, exists = system.ProcessRegistry.Get(system.NewLocalPID("foo/router"))
+ assert.False(t, exists)
+}
diff --git a/router/consistent_hash_router.go b/router/consistent_hash_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..36fd6913fd9fb3592a548441c66de75627d32d6e
--- /dev/null
+++ b/router/consistent_hash_router.go
@@ -0,0 +1,100 @@
+package router
+
+import (
+ "log"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/serialx/hashring"
+)
+
+type Hasher interface {
+ Hash() string
+}
+
+type consistentHashGroupRouter struct {
+ GroupRouter
+}
+
+type consistentHashPoolRouter struct {
+ PoolRouter
+}
+
+type hashmapContainer struct {
+ hashring *hashring.HashRing
+ routeeMap map[string]*actor.PID
+}
+type consistentHashRouterState struct {
+ hmc *hashmapContainer
+ sender actor.SenderContext
+}
+
+func (state *consistentHashRouterState) SetSender(sender actor.SenderContext) {
+ state.sender = sender
+}
+
+func (state *consistentHashRouterState) SetRoutees(routees *actor.PIDSet) {
+ // lookup from node name to PID
+ hmc := hashmapContainer{}
+ hmc.routeeMap = make(map[string]*actor.PID)
+ nodes := make([]string, routees.Len())
+ routees.ForEach(func(i int, pid *actor.PID) {
+ nodeName := pid.Address + "@" + pid.Id
+ nodes[i] = nodeName
+ hmc.routeeMap[nodeName] = pid
+ })
+ // initialize hashring for mapping message keys to node names
+ hmc.hashring = hashring.New(nodes)
+ state.hmc = &hmc
+}
+
+func (state *consistentHashRouterState) GetRoutees() *actor.PIDSet {
+ var routees actor.PIDSet
+ hmc := state.hmc
+ for _, v := range hmc.routeeMap {
+ routees.Add(v)
+ }
+ return &routees
+}
+
+func (state *consistentHashRouterState) RouteMessage(message interface{}) {
+ _, uwpMsg, _ := actor.UnwrapEnvelope(message)
+ switch msg := uwpMsg.(type) {
+ case Hasher:
+ key := msg.Hash()
+ hmc := state.hmc
+
+ node, ok := hmc.hashring.GetNode(key)
+ if !ok {
+ log.Printf("[ROUTING] Consistent has router failed to derminate routee: %v", key)
+ return
+ }
+ if routee, ok := hmc.routeeMap[node]; ok {
+ state.sender.Send(routee, message)
+ } else {
+ log.Println("[ROUTING] Consistent router failed to resolve node", node)
+ }
+ default:
+ log.Println("[ROUTING] Message must implement router.Hasher", msg)
+ }
+}
+
+func (state *consistentHashRouterState) InvokeRouterManagementMessage(msg ManagementMessage, sender *actor.PID) {
+}
+
+func NewConsistentHashPool(size int, opts ...actor.PropsOption) *actor.Props {
+ return (&actor.Props{}).
+ Configure(actor.WithSpawnFunc(spawner(&consistentHashPoolRouter{PoolRouter{PoolSize: size}}))).
+ Configure(opts...)
+}
+
+func NewConsistentHashGroup(routees ...*actor.PID) *actor.Props {
+ return (&actor.Props{}).Configure(actor.WithSpawnFunc(spawner(&consistentHashGroupRouter{GroupRouter{Routees: actor.NewPIDSet(routees...)}})))
+}
+
+func (config *consistentHashPoolRouter) CreateRouterState() State {
+ return &consistentHashRouterState{}
+}
+
+func (config *consistentHashGroupRouter) CreateRouterState() State {
+ return &consistentHashRouterState{}
+}
diff --git a/router/consistent_hash_router_test.go b/router/consistent_hash_router_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ca3c478769944d596e1bc83727d7923f52ad7b2
--- /dev/null
+++ b/router/consistent_hash_router_test.go
@@ -0,0 +1,100 @@
+package router_test
+
+import (
+ "strconv"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/router"
+)
+
+var system = actor.NewActorSystem()
+
+type myMessage struct {
+ i int32
+ pid *actor.PID
+}
+
+type getRoutees struct {
+ pid *actor.PID
+}
+
+func (m *myMessage) Hash() string {
+ i := atomic.LoadInt32(&m.i)
+ return strconv.Itoa(int(i))
+}
+
+var wait sync.WaitGroup
+
+type (
+ routerActor struct{}
+ tellerActor struct{}
+ managerActor struct {
+ set []*actor.PID
+ rpid *actor.PID
+ }
+)
+
+func (state *routerActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *myMessage:
+ // log.Printf("%v got message %d", context.Self(), msg.i)
+ atomic.AddInt32(&msg.i, 1)
+ wait.Done()
+ }
+}
+
+func (state *tellerActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *myMessage:
+ for i := 0; i < 100; i++ {
+ context.Send(msg.pid, msg)
+ time.Sleep(10 * time.Millisecond)
+ }
+ }
+}
+
+func (state *managerActor) Receive(context actor.Context) {
+ switch msg := context.Message().(type) {
+ case *router.Routees:
+ state.set = msg.PIDs
+ for i, v := range state.set {
+ if i%2 == 0 {
+ context.Send(state.rpid, &router.RemoveRoutee{PID: v})
+ // log.Println(v)
+ } else {
+ props := actor.PropsFromProducer(func() actor.Actor { return &routerActor{} })
+ pid := context.Spawn(props)
+ context.Send(state.rpid, &router.AddRoutee{PID: pid})
+ // log.Println(v)
+ }
+ }
+ context.Send(context.Self(), &getRoutees{state.rpid})
+ case *getRoutees:
+ state.rpid = msg.pid
+ context.Request(msg.pid, &router.GetRoutees{})
+ }
+}
+
+func TestConcurrency(t *testing.T) {
+ if testing.Short() {
+ t.SkipNow()
+ }
+
+ wait.Add(100 * 1000)
+ rpid := system.Root.Spawn(router.NewConsistentHashPool(100).Configure(actor.WithProducer(func() actor.Actor { return &routerActor{} })))
+
+ props := actor.PropsFromProducer(func() actor.Actor { return &tellerActor{} })
+ for i := 0; i < 1000; i++ {
+ pid := system.Root.Spawn(props)
+ system.Root.Send(pid, &myMessage{int32(i), rpid})
+ }
+
+ props = actor.PropsFromProducer(func() actor.Actor { return &managerActor{} })
+ pid := system.Root.Spawn(props)
+ system.Root.Send(pid, &getRoutees{rpid})
+ wait.Wait()
+}
diff --git a/router/messages.go b/router/messages.go
new file mode 100644
index 0000000000000000000000000000000000000000..569369c2b16a3a226002e1e854e9892c8a11b233
--- /dev/null
+++ b/router/messages.go
@@ -0,0 +1,15 @@
+package router
+
+type ManagementMessage interface {
+ ManagementMessage()
+}
+
+type BroadcastMessage struct {
+ Message interface{}
+}
+
+func (*AddRoutee) ManagementMessage() {}
+func (*RemoveRoutee) ManagementMessage() {}
+func (*GetRoutees) ManagementMessage() {}
+func (*AdjustPoolSize) ManagementMessage() {}
+func (*BroadcastMessage) ManagementMessage() {}
diff --git a/router/process.go b/router/process.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4770b668dad9138d3ec04816f2032f24055d1cd
--- /dev/null
+++ b/router/process.go
@@ -0,0 +1,87 @@
+package router
+
+import (
+ "sync"
+ "sync/atomic"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+// process serves as a proxy to the router implementation and forwards messages directly to the routee. This
+// optimization avoids serializing router messages through an actor
+type process struct {
+ parent *actor.PID
+ router *actor.PID
+ state State
+ mu sync.Mutex
+ watchers actor.PIDSet
+ stopping int32
+ actorSystem *actor.ActorSystem
+}
+
+var _ actor.Process = &process{}
+
+func (ref *process) SendUserMessage(pid *actor.PID, message interface{}) {
+ _, msg, _ := actor.UnwrapEnvelope(message)
+ if _, ok := msg.(ManagementMessage); !ok {
+ ref.state.RouteMessage(message)
+ } else {
+ r, _ := ref.actorSystem.ProcessRegistry.Get(ref.router)
+ // Always send the original message to the router actor,
+ // since if the message is enveloped, the sender need to get a response.
+ r.SendUserMessage(pid, message)
+ }
+}
+
+func (ref *process) SendSystemMessage(pid *actor.PID, message interface{}) {
+ switch msg := message.(type) {
+ case *actor.Watch:
+ if atomic.LoadInt32(&ref.stopping) == 1 {
+ if r, ok := ref.actorSystem.ProcessRegistry.Get(msg.Watcher); ok {
+ r.SendSystemMessage(msg.Watcher, &actor.Terminated{Who: pid})
+ }
+ return
+ }
+ ref.mu.Lock()
+ ref.watchers.Add(msg.Watcher)
+ ref.mu.Unlock()
+
+ case *actor.Unwatch:
+ ref.mu.Lock()
+ ref.watchers.Remove(msg.Watcher)
+ ref.mu.Unlock()
+
+ case *actor.Stop:
+ term := &actor.Terminated{Who: pid}
+ ref.mu.Lock()
+ ref.watchers.ForEach(func(_ int, other *actor.PID) {
+ if !other.Equal(ref.parent) {
+ if r, ok := ref.actorSystem.ProcessRegistry.Get(other); ok {
+ r.SendSystemMessage(other, term)
+ }
+ }
+ })
+ // Notify parent
+ if ref.parent != nil {
+ if r, ok := ref.actorSystem.ProcessRegistry.Get(ref.parent); ok {
+ r.SendSystemMessage(ref.parent, term)
+ }
+ }
+ ref.mu.Unlock()
+
+ default:
+ r, _ := ref.actorSystem.ProcessRegistry.Get(ref.router)
+ r.SendSystemMessage(pid, message)
+
+ }
+}
+
+func (ref *process) Stop(pid *actor.PID) {
+ if atomic.SwapInt32(&ref.stopping, 1) == 1 {
+ return
+ }
+
+ _ = ref.actorSystem.Root.StopFuture(ref.router).Wait()
+ ref.actorSystem.ProcessRegistry.Remove(pid)
+ ref.SendSystemMessage(pid, &actor.Stop{})
+}
diff --git a/router/process_test.go b/router/process_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f44d11d637adc93ed8f46736f7a5efd2cb6fec04
--- /dev/null
+++ b/router/process_test.go
@@ -0,0 +1,90 @@
+package router
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/mock"
+)
+
+var (
+ _ fmt.Formatter
+ _ time.Time
+)
+
+// TODO fix this
+func __TestRouterSendsUserMessageToChild(t *testing.T) {
+ child, p := spawnMockProcess("child")
+ defer removeMockProcess(child)
+
+ p.On("SendUserMessage", mock.Anything, mock.MatchedBy(func(env interface{}) bool {
+ _, msg, _ := actor.UnwrapEnvelope(env)
+ return msg.(string) == "hello"
+ }))
+ p.On("SendSystemMessage", mock.Anything, mock.Anything)
+
+ s1 := actor.NewPIDSet(child)
+
+ rs := new(testRouterState)
+ // rs.On("SetSender",)
+ rs.On("SetRoutees", s1)
+ rs.On("RouteMessage", mock.MatchedBy(func(env interface{}) bool {
+ _, msg, _ := actor.UnwrapEnvelope(env)
+ return msg.(string) == "hello"
+ }), mock.Anything)
+
+ grc := newGroupRouterConfig(child)
+ grc.On("CreateRouterState").Return(rs)
+
+ routerPID := system.Root.Spawn((&actor.Props{}).Configure(actor.WithSpawnFunc(spawner(grc))))
+ system.Root.Send(routerPID, "hello")
+ system.Root.RequestWithCustomSender(routerPID, "hello", routerPID)
+
+ mock.AssertExpectationsForObjects(t, p, rs)
+}
+
+type testGroupRouter struct {
+ GroupRouter
+ mock.Mock
+}
+
+func newGroupRouterConfig(routees ...*actor.PID) *testGroupRouter {
+ r := new(testGroupRouter)
+ r.Routees = actor.NewPIDSet(routees...)
+ return r
+}
+
+func (m *testGroupRouter) CreateRouterState() State {
+ args := m.Called()
+ return args.Get(0).(*testRouterState)
+}
+
+type testRouterState struct {
+ mock.Mock
+ routees *actor.PIDSet
+ sender actor.SenderContext
+}
+
+func (m *testRouterState) SetSender(sender actor.SenderContext) {
+ m.Called(sender)
+ m.sender = sender
+}
+
+func (m *testRouterState) SetRoutees(routees *actor.PIDSet) {
+ m.Called(routees)
+ m.routees = routees
+}
+
+func (m *testRouterState) RouteMessage(message interface{}) {
+ m.Called(message)
+ m.routees.ForEach(func(i int, pid *actor.PID) {
+ system.Root.Send(pid, message)
+ })
+}
+
+func (m *testRouterState) GetRoutees() *actor.PIDSet {
+ args := m.Called()
+ return args.Get(0).(*actor.PIDSet)
+}
diff --git a/router/random_router.go b/router/random_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..02a4745534dc6d7095716a30a5c48bef3efd7e10
--- /dev/null
+++ b/router/random_router.go
@@ -0,0 +1,62 @@
+package router
+
+import (
+ "math/rand"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type randomGroupRouter struct {
+ GroupRouter
+}
+
+type randomPoolRouter struct {
+ PoolRouter
+}
+
+type randomRouterState struct {
+ routees *actor.PIDSet
+ sender actor.SenderContext
+}
+
+func (state *randomRouterState) SetSender(sender actor.SenderContext) {
+ state.sender = sender
+}
+
+func (state *randomRouterState) SetRoutees(routees *actor.PIDSet) {
+ state.routees = routees
+}
+
+func (state *randomRouterState) GetRoutees() *actor.PIDSet {
+ return state.routees
+}
+
+func (state *randomRouterState) RouteMessage(message interface{}) {
+ pid := randomRoutee(state.routees)
+ state.sender.Send(pid, message)
+}
+
+func NewRandomPool(size int, opts ...actor.PropsOption) *actor.Props {
+ return (&actor.Props{}).
+ Configure(actor.WithSpawnFunc(spawner(&randomPoolRouter{PoolRouter{PoolSize: size}}))).
+ Configure(opts...)
+}
+
+func NewRandomGroup(routees ...*actor.PID) *actor.Props {
+ return (&actor.Props{}).Configure(actor.WithSpawnFunc(spawner(&randomGroupRouter{GroupRouter{Routees: actor.NewPIDSet(routees...)}})))
+}
+
+func (config *randomPoolRouter) CreateRouterState() State {
+ return &randomRouterState{}
+}
+
+func (config *randomGroupRouter) CreateRouterState() State {
+ return &randomRouterState{}
+}
+
+func randomRoutee(routees *actor.PIDSet) *actor.PID {
+ l := routees.Len()
+ r := rand.Intn(l)
+ pid := routees.Get(r)
+ return pid
+}
diff --git a/router/roundrobin_router.go b/router/roundrobin_router.go
new file mode 100644
index 0000000000000000000000000000000000000000..dbce59d34d2c0c737a37755e505ec2ad59d34a0f
--- /dev/null
+++ b/router/roundrobin_router.go
@@ -0,0 +1,67 @@
+package router
+
+import (
+ "sync/atomic"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type roundRobinGroupRouter struct {
+ GroupRouter
+}
+
+type roundRobinPoolRouter struct {
+ PoolRouter
+}
+
+type roundRobinState struct {
+ index int32
+ routees *actor.PIDSet
+ sender actor.SenderContext
+}
+
+func (state *roundRobinState) SetSender(sender actor.SenderContext) {
+ state.sender = sender
+}
+
+func (state *roundRobinState) SetRoutees(routees *actor.PIDSet) {
+ state.routees = routees
+}
+
+func (state *roundRobinState) GetRoutees() *actor.PIDSet {
+ return state.routees
+}
+
+func (state *roundRobinState) RouteMessage(message interface{}) {
+ pid := roundRobinRoutee(&state.index, state.routees)
+ state.sender.Send(pid, message)
+}
+
+func NewRoundRobinPool(size int, opts ...actor.PropsOption) *actor.Props {
+ return (&actor.Props{}).
+ Configure(actor.WithSpawnFunc(spawner(&roundRobinPoolRouter{PoolRouter{PoolSize: size}}))).
+ Configure(opts...)
+}
+
+func NewRoundRobinGroup(routees ...*actor.PID) *actor.Props {
+ return (&actor.Props{}).Configure(actor.WithSpawnFunc(spawner(&roundRobinGroupRouter{GroupRouter{Routees: actor.NewPIDSet(routees...)}})))
+}
+
+func (config *roundRobinPoolRouter) CreateRouterState() State {
+ return &roundRobinState{}
+}
+
+func (config *roundRobinGroupRouter) CreateRouterState() State {
+ return &roundRobinState{}
+}
+
+func roundRobinRoutee(index *int32, routees *actor.PIDSet) *actor.PID {
+ i := int(atomic.AddInt32(index, 1))
+ if i < 0 {
+ *index = 0
+ i = 0
+ }
+ mod := routees.Len()
+ routee := routees.Get(i % mod)
+ return routee
+}
diff --git a/router/router.go b/router/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e0fa9eec59e5197ee988f10b7c525233549c838
--- /dev/null
+++ b/router/router.go
@@ -0,0 +1,11 @@
+package router
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+// A type that satisfies router.Interface can be used as a router
+type State interface {
+ RouteMessage(message interface{})
+ SetRoutees(routees *actor.PIDSet)
+ GetRoutees() *actor.PIDSet
+ SetSender(sender actor.SenderContext)
+}
diff --git a/router/routeractor_group.go b/router/routeractor_group.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e502b42fed84c496cd7465574422958f77bdb15
--- /dev/null
+++ b/router/routeractor_group.go
@@ -0,0 +1,57 @@
+package router
+
+import (
+ "sync"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type groupRouterActor struct {
+ props *actor.Props
+ config RouterConfig
+ state State
+ wg *sync.WaitGroup
+}
+
+func (a *groupRouterActor) Receive(context actor.Context) {
+ switch m := context.Message().(type) {
+ case *actor.Started:
+ a.config.OnStarted(context, a.props, a.state)
+ a.wg.Done()
+
+ case *AddRoutee:
+ r := a.state.GetRoutees()
+ if r.Contains(m.PID) {
+ return
+ }
+ context.Watch(m.PID)
+ r.Add(m.PID)
+ a.state.SetRoutees(r)
+
+ case *RemoveRoutee:
+ r := a.state.GetRoutees()
+ if !r.Contains(m.PID) {
+ return
+ }
+
+ context.Unwatch(m.PID)
+ r.Remove(m.PID)
+ a.state.SetRoutees(r)
+
+ case *BroadcastMessage:
+ msg := m.Message
+ sender := context.Sender()
+ a.state.GetRoutees().ForEach(func(i int, pid *actor.PID) {
+ context.RequestWithCustomSender(pid, msg, sender)
+ })
+
+ case *GetRoutees:
+ r := a.state.GetRoutees()
+ routees := make([]*actor.PID, r.Len())
+ r.ForEach(func(i int, pid *actor.PID) {
+ routees[i] = pid
+ })
+
+ context.Respond(&Routees{PIDs: routees})
+ }
+}
diff --git a/router/routeractor_group_test.go b/router/routeractor_group_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ab0849d36a4ae6ba765abeba74dfa9f7b8126f1
--- /dev/null
+++ b/router/routeractor_group_test.go
@@ -0,0 +1,90 @@
+package router
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/mock"
+)
+
+func TestGroupRouterActor_Receive_AddRoute(t *testing.T) {
+ state := new(testRouterState)
+
+ a := groupRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ c := new(mockContext)
+ c.On("Message").Return(&AddRoutee{PID: p1})
+ c.On("Watch", p1).Once()
+
+ state.On("GetRoutees").Return(&actor.PIDSet{})
+ state.On("SetRoutees", actor.NewPIDSet(p1)).Once()
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestGroupRouterActor_Receive_AddRoute_NoDuplicates(t *testing.T) {
+ state := new(testRouterState)
+
+ a := groupRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ c := new(mockContext)
+ c.On("Message").Return(&AddRoutee{PID: p1})
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1))
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestGroupRouterActor_Receive_RemoveRoute(t *testing.T) {
+ state := new(testRouterState)
+
+ a := groupRouterActor{state: state}
+
+ p1, _ := spawnMockProcess("p1")
+ defer removeMockProcess(p1)
+
+ p2 := system.NewLocalPID("p2")
+ c := new(mockContext)
+ c.On("Message").Return(&RemoveRoutee{PID: p1})
+ c.On("Unwatch", p1).
+ Run(func(args mock.Arguments) {
+ }).Once()
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1, p2))
+ state.On("SetRoutees", actor.NewPIDSet(p2)).Once()
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestGroupRouterActor_Receive_BroadcastMessage(t *testing.T) {
+ state := new(testRouterState)
+ a := groupRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ p2 := system.NewLocalPID("p2")
+
+ child := new(mockProcess)
+ child.On("SendUserMessage", mock.Anything, mock.Anything).Times(2)
+
+ system.ProcessRegistry.Add(child, "p1")
+ system.ProcessRegistry.Add(child, "p2")
+ defer func() {
+ system.ProcessRegistry.Remove(&actor.PID{Id: "p1"})
+ system.ProcessRegistry.Remove(&actor.PID{Id: "p2"})
+ }()
+
+ c := new(mockContext)
+ c.On("Message").Return(&BroadcastMessage{"hi"})
+ c.On("Sender").Return((*actor.PID)(nil))
+ c.On("RequestWithCustomSender").Twice()
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1, p2))
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c, child)
+}
diff --git a/router/routeractor_pool.go b/router/routeractor_pool.go
new file mode 100644
index 0000000000000000000000000000000000000000..c4defa76df0a29df48cfc1b310000df76b6d6791
--- /dev/null
+++ b/router/routeractor_pool.go
@@ -0,0 +1,73 @@
+package router
+
+import (
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type poolRouterActor struct {
+ props *actor.Props
+ config RouterConfig
+ state State
+ wg *sync.WaitGroup
+}
+
+func (a *poolRouterActor) Receive(context actor.Context) {
+ switch m := context.Message().(type) {
+ case *actor.Started:
+ a.config.OnStarted(context, a.props, a.state)
+ a.wg.Done()
+
+ case *AddRoutee:
+ r := a.state.GetRoutees()
+ if r.Contains(m.PID) {
+ return
+ }
+ context.Watch(m.PID)
+ r.Add(m.PID)
+ a.state.SetRoutees(r)
+
+ case *RemoveRoutee:
+ r := a.state.GetRoutees()
+ if !r.Contains(m.PID) {
+ return
+ }
+
+ context.Unwatch(m.PID)
+ r.Remove(m.PID)
+ a.state.SetRoutees(r)
+ // sleep for 1ms before sending the poison pill
+ // This is to give some time to the routee actor receive all
+ // the messages. Specially due to the synchronization conditions in
+ // consistent hash router, where a copy of hmc can be obtained before
+ // the update and cause messages routed to a dead routee if there is no
+ // delay. This is a best effort approach and 1ms seems to be acceptable
+ // in terms of both delay it cause to the router actor and the time it
+ // provides for the routee to receive messages before it dies.
+ time.Sleep(time.Millisecond * 1)
+ context.Send(m.PID, &actor.PoisonPill{})
+
+ case *BroadcastMessage:
+ msg := m.Message
+ sender := context.Sender()
+ a.state.GetRoutees().ForEach(func(i int, pid *actor.PID) {
+ context.RequestWithCustomSender(pid, msg, sender)
+ })
+
+ case *GetRoutees:
+ r := a.state.GetRoutees()
+ routees := make([]*actor.PID, r.Len())
+ r.ForEach(func(i int, pid *actor.PID) {
+ routees[i] = pid
+ })
+
+ context.Respond(&Routees{PIDs: routees})
+ case *actor.Terminated:
+ r := a.state.GetRoutees()
+ if r.Remove(m.Who) {
+ a.state.SetRoutees(r)
+ }
+ }
+}
diff --git a/router/routeractor_pool_test.go b/router/routeractor_pool_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1ff22feaa03c9534c04da70b5da0146d82be75f9
--- /dev/null
+++ b/router/routeractor_pool_test.go
@@ -0,0 +1,91 @@
+package router
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/mock"
+)
+
+func TestPoolRouterActor_Receive_AddRoute(t *testing.T) {
+ state := new(testRouterState)
+
+ a := poolRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ c := new(mockContext)
+ c.On("Message").Return(&AddRoutee{PID: p1})
+ c.On("Watch", p1).Once()
+
+ state.On("GetRoutees").Return(&actor.PIDSet{})
+ state.On("SetRoutees", actor.NewPIDSet(p1)).Once()
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestPoolRouterActor_Receive_AddRoute_NoDuplicates(t *testing.T) {
+ state := new(testRouterState)
+
+ a := poolRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ c := new(mockContext)
+ c.On("Message").Return(&AddRoutee{PID: p1})
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1))
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestPoolRouterActor_Receive_RemoveRoute(t *testing.T) {
+ state := new(testRouterState)
+
+ a := poolRouterActor{state: state}
+
+ p1, pr1 := spawnMockProcess("p1")
+ defer removeMockProcess(p1)
+ pr1.On("SendUserMessage", p1, &actor.PoisonPill{}).Once()
+
+ p2 := system.NewLocalPID("p2")
+ c := new(mockContext)
+ c.On("Message").Return(&RemoveRoutee{PID: p1})
+ c.On("Unwatch", p1).Once()
+
+ c.On("Send")
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1, p2))
+ state.On("SetRoutees", actor.NewPIDSet(p2)).Once()
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c)
+}
+
+func TestPoolRouterActor_Receive_BroadcastMessage(t *testing.T) {
+ state := new(testRouterState)
+ a := poolRouterActor{state: state}
+
+ p1 := system.NewLocalPID("p1")
+ p2 := system.NewLocalPID("p2")
+
+ child := new(mockProcess)
+ child.On("SendUserMessage", mock.Anything, mock.Anything).Times(2)
+
+ system.ProcessRegistry.Add(child, "p1")
+ system.ProcessRegistry.Add(child, "p2")
+ defer func() {
+ system.ProcessRegistry.Remove(&actor.PID{Id: "p1"})
+ system.ProcessRegistry.Remove(&actor.PID{Id: "p2"})
+ }()
+
+ c := new(mockContext)
+ c.On("Message").Return(&BroadcastMessage{"hi"})
+ c.On("Sender").Return((*actor.PID)(nil))
+ c.On("RequestWithCustomSender").Twice()
+
+ state.On("GetRoutees").Return(actor.NewPIDSet(p1, p2))
+
+ a.Receive(c)
+ mock.AssertExpectationsForObjects(t, state, c, child)
+}
diff --git a/router/routercontracts.pb.go b/router/routercontracts.pb.go
new file mode 100644
index 0000000000000000000000000000000000000000..febbb8aab36a63d6c0a40f140d7ecbe11ea88189
--- /dev/null
+++ b/router/routercontracts.pb.go
@@ -0,0 +1,390 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.27.1
+// protoc v3.19.1
+// source: routercontracts.proto
+
+package router
+
+import (
+ actor "gitee.com/simplexyz/simpleactor-go/actor"
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type AddRoutee struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PID *actor.PID `protobuf:"bytes,1,opt,name=PID,proto3" json:"PID,omitempty"`
+}
+
+func (x *AddRoutee) Reset() {
+ *x = AddRoutee{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_routercontracts_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AddRoutee) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddRoutee) ProtoMessage() {}
+
+func (x *AddRoutee) ProtoReflect() protoreflect.Message {
+ mi := &file_routercontracts_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddRoutee.ProtoReflect.Descriptor instead.
+func (*AddRoutee) Descriptor() ([]byte, []int) {
+ return file_routercontracts_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *AddRoutee) GetPID() *actor.PID {
+ if x != nil {
+ return x.PID
+ }
+ return nil
+}
+
+type RemoveRoutee struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PID *actor.PID `protobuf:"bytes,1,opt,name=PID,proto3" json:"PID,omitempty"`
+}
+
+func (x *RemoveRoutee) Reset() {
+ *x = RemoveRoutee{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_routercontracts_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *RemoveRoutee) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoveRoutee) ProtoMessage() {}
+
+func (x *RemoveRoutee) ProtoReflect() protoreflect.Message {
+ mi := &file_routercontracts_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoveRoutee.ProtoReflect.Descriptor instead.
+func (*RemoveRoutee) Descriptor() ([]byte, []int) {
+ return file_routercontracts_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *RemoveRoutee) GetPID() *actor.PID {
+ if x != nil {
+ return x.PID
+ }
+ return nil
+}
+
+type AdjustPoolSize struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Change int32 `protobuf:"varint,1,opt,name=change,proto3" json:"change,omitempty"`
+}
+
+func (x *AdjustPoolSize) Reset() {
+ *x = AdjustPoolSize{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_routercontracts_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AdjustPoolSize) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AdjustPoolSize) ProtoMessage() {}
+
+func (x *AdjustPoolSize) ProtoReflect() protoreflect.Message {
+ mi := &file_routercontracts_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AdjustPoolSize.ProtoReflect.Descriptor instead.
+func (*AdjustPoolSize) Descriptor() ([]byte, []int) {
+ return file_routercontracts_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *AdjustPoolSize) GetChange() int32 {
+ if x != nil {
+ return x.Change
+ }
+ return 0
+}
+
+type GetRoutees struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+}
+
+func (x *GetRoutees) Reset() {
+ *x = GetRoutees{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_routercontracts_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *GetRoutees) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetRoutees) ProtoMessage() {}
+
+func (x *GetRoutees) ProtoReflect() protoreflect.Message {
+ mi := &file_routercontracts_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetRoutees.ProtoReflect.Descriptor instead.
+func (*GetRoutees) Descriptor() ([]byte, []int) {
+ return file_routercontracts_proto_rawDescGZIP(), []int{3}
+}
+
+type Routees struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ PIDs []*actor.PID `protobuf:"bytes,1,rep,name=PIDs,proto3" json:"PIDs,omitempty"`
+}
+
+func (x *Routees) Reset() {
+ *x = Routees{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_routercontracts_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Routees) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Routees) ProtoMessage() {}
+
+func (x *Routees) ProtoReflect() protoreflect.Message {
+ mi := &file_routercontracts_proto_msgTypes[4]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Routees.ProtoReflect.Descriptor instead.
+func (*Routees) Descriptor() ([]byte, []int) {
+ return file_routercontracts_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Routees) GetPIDs() []*actor.PID {
+ if x != nil {
+ return x.PIDs
+ }
+ return nil
+}
+
+var File_routercontracts_proto protoreflect.FileDescriptor
+
+var file_routercontracts_proto_rawDesc = []byte{
+ 0x0a, 0x15, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74,
+ 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x1a,
+ 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x29, 0x0a, 0x09,
+ 0x41, 0x64, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x65, 0x12, 0x1c, 0x0a, 0x03, 0x50, 0x49, 0x44,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x03, 0x50, 0x49, 0x44, 0x22, 0x2c, 0x0a, 0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x76,
+ 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x65, 0x12, 0x1c, 0x0a, 0x03, 0x50, 0x49, 0x44, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50, 0x49, 0x44,
+ 0x52, 0x03, 0x50, 0x49, 0x44, 0x22, 0x28, 0x0a, 0x0e, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x50,
+ 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67,
+ 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22,
+ 0x0c, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x65, 0x73, 0x22, 0x29, 0x0a,
+ 0x07, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x50, 0x49, 0x44, 0x73,
+ 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x50,
+ 0x49, 0x44, 0x52, 0x04, 0x50, 0x49, 0x44, 0x73, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68,
+ 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x73, 0x79, 0x6e, 0x6b, 0x72, 0x6f, 0x6e, 0x2f,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x2d, 0x67, 0x6f, 0x2f, 0x72, 0x6f,
+ 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+ file_routercontracts_proto_rawDescOnce sync.Once
+ file_routercontracts_proto_rawDescData = file_routercontracts_proto_rawDesc
+)
+
+func file_routercontracts_proto_rawDescGZIP() []byte {
+ file_routercontracts_proto_rawDescOnce.Do(func() {
+ file_routercontracts_proto_rawDescData = protoimpl.X.CompressGZIP(file_routercontracts_proto_rawDescData)
+ })
+ return file_routercontracts_proto_rawDescData
+}
+
+var file_routercontracts_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_routercontracts_proto_goTypes = []interface{}{
+ (*AddRoutee)(nil), // 0: router.AddRoutee
+ (*RemoveRoutee)(nil), // 1: router.RemoveRoutee
+ (*AdjustPoolSize)(nil), // 2: router.AdjustPoolSize
+ (*GetRoutees)(nil), // 3: router.GetRoutees
+ (*Routees)(nil), // 4: router.Routees
+ (*actor.PID)(nil), // 5: actor.PID
+}
+var file_routercontracts_proto_depIdxs = []int32{
+ 5, // 0: router.AddRoutee.PID:type_name -> actor.PID
+ 5, // 1: router.RemoveRoutee.PID:type_name -> actor.PID
+ 5, // 2: router.Routees.PIDs:type_name -> actor.PID
+ 3, // [3:3] is the sub-list for method output_type
+ 3, // [3:3] is the sub-list for method input_type
+ 3, // [3:3] is the sub-list for extension type_name
+ 3, // [3:3] is the sub-list for extension extendee
+ 0, // [0:3] is the sub-list for field type_name
+}
+
+func init() { file_routercontracts_proto_init() }
+func file_routercontracts_proto_init() {
+ if File_routercontracts_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_routercontracts_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AddRoutee); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_routercontracts_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*RemoveRoutee); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_routercontracts_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AdjustPoolSize); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_routercontracts_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*GetRoutees); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_routercontracts_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Routees); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_routercontracts_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 5,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_routercontracts_proto_goTypes,
+ DependencyIndexes: file_routercontracts_proto_depIdxs,
+ MessageInfos: file_routercontracts_proto_msgTypes,
+ }.Build()
+ File_routercontracts_proto = out.File
+ file_routercontracts_proto_rawDesc = nil
+ file_routercontracts_proto_goTypes = nil
+ file_routercontracts_proto_depIdxs = nil
+}
diff --git a/router/routercontracts.proto b/router/routercontracts.proto
new file mode 100644
index 0000000000000000000000000000000000000000..75a6ee879efe58c0fd76728c0726c65dfdd58eee
--- /dev/null
+++ b/router/routercontracts.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+package router;
+option go_package = "gitee.com/simplexyz/simpleactor-go/router";
+import "actor.proto";
+
+message AddRoutee {
+ actor.PID PID = 1;
+}
+
+message RemoveRoutee {
+ actor.PID PID = 1;
+}
+
+message AdjustPoolSize {
+ int32 change = 1;
+}
+
+message GetRoutees {}
+
+message Routees {
+ repeated actor.PID PIDs = 1;
+}
diff --git a/scheduler/timer.go b/scheduler/timer.go
new file mode 100644
index 0000000000000000000000000000000000000000..a958c7acc00100b84dfee78fcb588b55b0fa263c
--- /dev/null
+++ b/scheduler/timer.go
@@ -0,0 +1,108 @@
+package scheduler
+
+import (
+ "runtime"
+ "sync/atomic"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+)
+
+type CancelFunc func()
+
+type Stopper interface {
+ Stop()
+}
+
+const (
+ stateInit = iota
+ stateReady
+ stateDone
+)
+
+func startTimer(delay, interval time.Duration, fn func()) CancelFunc {
+ var t *time.Timer
+ var state int32
+ t = time.AfterFunc(delay, func() {
+ for atomic.LoadInt32(&state) == stateInit {
+ runtime.Gosched()
+ }
+
+ if state == stateDone {
+ return
+ }
+
+ fn()
+ t.Reset(interval)
+ })
+
+ // ensures t != nil and is required to avoid data race in
+ // AfterFunc calling t.Reset
+ atomic.StoreInt32(&state, stateReady)
+
+ return func() {
+ if atomic.SwapInt32(&state, stateDone) != stateDone {
+ t.Stop()
+ }
+ }
+}
+
+// A scheduler utilizing timers to send messages in the future and at regular intervals.
+type TimerScheduler struct {
+ ctx actor.SenderContext
+}
+
+type timerOptionFunc func(*TimerScheduler)
+
+// WithContext configures the scheduler to use ctx rather than the default,
+// EmptyRootContext.
+func WithContext(ctx actor.SenderContext) timerOptionFunc {
+ return func(s *TimerScheduler) {
+ s.ctx = ctx
+ }
+}
+
+// NewTimerScheduler creates a new scheduler using the EmptyRootContext.
+// Additional options may be specified to override the default behavior.
+func NewTimerScheduler(sender actor.SenderContext, opts ...timerOptionFunc) *TimerScheduler {
+ s := &TimerScheduler{ctx: sender}
+ for _, opt := range opts {
+ opt(s)
+ }
+ return s
+}
+
+// SendOnce waits for the duration to elapse and then calls actor.SenderContext.Send to forward the message to pid.
+func (s *TimerScheduler) SendOnce(delay time.Duration, pid *actor.PID, message interface{}) CancelFunc {
+ t := time.AfterFunc(delay, func() {
+ s.ctx.Send(pid, message)
+ })
+
+ return func() { t.Stop() }
+}
+
+// SendRepeatedly waits for the initial duration to elapse and then calls Send to forward the message to pid
+// repeatedly for each interval.
+func (s *TimerScheduler) SendRepeatedly(initial, interval time.Duration, pid *actor.PID, message interface{}) CancelFunc {
+ return startTimer(initial, interval, func() {
+ s.ctx.Send(pid, message)
+ })
+}
+
+// RequestOnce waits for the duration to elapse and then calls actor.SenderContext.Request to forward the message to
+// pid.
+func (s *TimerScheduler) RequestOnce(delay time.Duration, pid *actor.PID, message interface{}) CancelFunc {
+ t := time.AfterFunc(delay, func() {
+ s.ctx.Request(pid, message)
+ })
+
+ return func() { t.Stop() }
+}
+
+// RequestRepeatedly waits for the initial duration to elapse and then calls Request to forward the message to pid
+// repeatedly for each interval.
+func (s *TimerScheduler) RequestRepeatedly(delay, interval time.Duration, pid *actor.PID, message interface{}) CancelFunc {
+ return startTimer(delay, interval, func() {
+ s.ctx.Request(pid, message)
+ })
+}
diff --git a/scheduler/timer_example_test.go b/scheduler/timer_example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8a88d0f0ca97beb0e2d465602113485dc07c052
--- /dev/null
+++ b/scheduler/timer_example_test.go
@@ -0,0 +1,40 @@
+package scheduler_test
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "gitee.com/simplexyz/simpleactor-go/scheduler"
+)
+
+var system = actor.NewActorSystem()
+
+// Use the timer scheduler to repeatedly send messages to an actor.
+func ExampleTimerScheduler_sendRepeatedly() {
+ var wg sync.WaitGroup
+
+ wg.Add(2)
+
+ count := 0
+ props := actor.PropsFromFunc(func(c actor.Context) {
+ if v, ok := c.Message().(string); ok {
+ count++
+ fmt.Println(count, v)
+ wg.Done()
+ }
+ })
+
+ pid := system.Root.Spawn(props)
+
+ s := scheduler.NewTimerScheduler(system.Root)
+ cancel := s.SendRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, "Hello")
+
+ wg.Wait()
+ cancel()
+
+ // Output:
+ // 1 Hello
+ // 2 Hello
+}
diff --git a/scheduler/timer_test.go b/scheduler/timer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..49499bf159001673968eba37e5890321c4fe8d15
--- /dev/null
+++ b/scheduler/timer_test.go
@@ -0,0 +1,115 @@
+package scheduler
+
+import (
+ "testing"
+ "time"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+var system = actor.NewActorSystem()
+
+func TestNewTimerScheduler(t *testing.T) {
+ newActor := func(t *testing.T, n int) (pid *actor.PID, ch chan struct{}) {
+ ch = make(chan struct{}, n)
+ props := actor.PropsFromFunc(func(c actor.Context) {
+ switch c.Message().(type) {
+ case string:
+ select {
+ case ch <- struct{}{}:
+ default:
+ t.Errorf("exceeeded expected count %d", n)
+ }
+ }
+ })
+ return system.Root.Spawn(props), ch
+ }
+
+ // check verifies the number of times ch receives a message matches exp
+ // and executes once more to ensure no further messages are received
+ check := func(t *testing.T, ch chan struct{}, cancel CancelFunc, exp int) {
+ got := 0
+ for i := 0; i < exp+1; i++ {
+ select {
+ case <-ch:
+ got++
+ if got == exp {
+ cancel()
+ }
+ case <-time.After(3 * time.Millisecond):
+ if got != exp {
+ assert.Fail(t, "failed to receive message")
+ }
+ }
+ }
+ cancel()
+ assert.Equal(t, exp, got)
+ }
+
+ t.Run("does", func(t *testing.T) {
+ t.Run("send once", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 1)
+ tok := s.SendOnce(1*time.Millisecond, pid, "hello")
+
+ check(t, ch, tok, 1)
+ })
+
+ t.Run("send repeatedly", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 5)
+ tok := s.SendRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, "hello")
+ check(t, ch, tok, 5)
+ })
+
+ t.Run("request once", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 1)
+ tok := s.RequestOnce(1*time.Millisecond, pid, "hello")
+
+ check(t, ch, tok, 1)
+ })
+
+ t.Run("request repeatedly", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 5)
+ tok := s.RequestRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, "hello")
+ check(t, ch, tok, 5)
+ })
+ })
+
+ t.Run("does not", func(t *testing.T) {
+ t.Run("send once", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 1)
+ cancel := s.SendOnce(1*time.Millisecond, pid, "hello")
+ cancel()
+ check(t, ch, cancel, 0)
+ })
+
+ t.Run("send repeatedly", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 5)
+ cancel := s.SendRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, "hello")
+ cancel()
+ check(t, ch, cancel, 0)
+ })
+
+ t.Run("request once", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 1)
+ cancel := s.RequestOnce(1*time.Millisecond, pid, "hello")
+ cancel()
+ check(t, ch, cancel, 0)
+ })
+
+ t.Run("request repeatedly", func(t *testing.T) {
+ s := NewTimerScheduler(system.Root)
+ pid, ch := newActor(t, 5)
+ cancel := s.RequestRepeatedly(1*time.Millisecond, 1*time.Millisecond, pid, "hello")
+ cancel()
+ check(t, ch, cancel, 0)
+ })
+ })
+}
diff --git a/stream/typed.go b/stream/typed.go
new file mode 100644
index 0000000000000000000000000000000000000000..7fa3031d6ca34c37b568c0e3653fe1ea30c5ad02
--- /dev/null
+++ b/stream/typed.go
@@ -0,0 +1,42 @@
+package stream
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+type TypedStream[T any] struct {
+ c chan T
+ pid *actor.PID
+ actorSystem *actor.ActorSystem
+}
+
+func (s *TypedStream[T]) C() <-chan T {
+ return s.c
+}
+
+func (s *TypedStream[T]) PID() *actor.PID {
+ return s.pid
+}
+
+func (s *TypedStream[T]) Close() {
+ s.actorSystem.Root.Stop(s.pid)
+ close(s.c)
+}
+
+func NewTypedStream[T any](actorSystem *actor.ActorSystem) *TypedStream[T] {
+ c := make(chan T)
+
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case actor.AutoReceiveMessage, actor.SystemMessage:
+ // ignore terminate
+ case T:
+ c <- msg
+ }
+ })
+ pid := actorSystem.Root.Spawn(props)
+
+ return &TypedStream[T]{
+ c: c,
+ pid: pid,
+ actorSystem: actorSystem,
+ }
+}
diff --git a/stream/typed_test.go b/stream/typed_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c1277a4ce31b0785712bf43d10dfcf93b8136a6e
--- /dev/null
+++ b/stream/typed_test.go
@@ -0,0 +1,22 @@
+package stream
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReceiveFromTypedStream(t *testing.T) {
+ system := actor.NewActorSystem()
+ s := NewTypedStream[string](system)
+ go func() {
+ rootContext := system.Root
+ rootContext.Send(s.PID(), "hello")
+ rootContext.Send(s.PID(), "you")
+ }()
+ res := <-s.C()
+ res2 := <-s.C()
+ assert.Equal(t, "hello", res)
+ assert.Equal(t, "you", res2)
+}
diff --git a/stream/untyped.go b/stream/untyped.go
new file mode 100644
index 0000000000000000000000000000000000000000..16cf6de92e6561824b022e9aa6aab65b3a19baea
--- /dev/null
+++ b/stream/untyped.go
@@ -0,0 +1,42 @@
+package stream
+
+import "gitee.com/simplexyz/simpleactor-go/actor"
+
+type UntypedStream struct {
+ c chan interface{}
+ pid *actor.PID
+ actorSystem *actor.ActorSystem
+}
+
+func (s *UntypedStream) C() <-chan interface{} {
+ return s.c
+}
+
+func (s *UntypedStream) PID() *actor.PID {
+ return s.pid
+}
+
+func (s *UntypedStream) Close() {
+ s.actorSystem.Root.Stop(s.pid)
+ close(s.c)
+}
+
+func NewUntypedStream(actorSystem *actor.ActorSystem) *UntypedStream {
+ c := make(chan interface{})
+
+ props := actor.PropsFromFunc(func(ctx actor.Context) {
+ switch msg := ctx.Message().(type) {
+ case actor.AutoReceiveMessage, actor.SystemMessage:
+ // ignore terminate
+ default:
+ c <- msg
+ }
+ })
+ pid := actorSystem.Root.Spawn(props)
+
+ return &UntypedStream{
+ c: c,
+ pid: pid,
+ actorSystem: actorSystem,
+ }
+}
diff --git a/stream/untyped_test.go b/stream/untyped_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..df4f6c56a5e5c72c50d4da205c472f546934d760
--- /dev/null
+++ b/stream/untyped_test.go
@@ -0,0 +1,22 @@
+package stream
+
+import (
+ "testing"
+
+ "gitee.com/simplexyz/simpleactor-go/actor"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestReceiveFromStream(t *testing.T) {
+ system := actor.NewActorSystem()
+ s := NewUntypedStream(system)
+ go func() {
+ rootContext := system.Root
+ rootContext.Send(s.PID(), "hello")
+ rootContext.Send(s.PID(), "you")
+ }()
+ res := <-s.C()
+ res2 := <-s.C()
+ assert.Equal(t, "hello", res.(string))
+ assert.Equal(t, "you", res2.(string))
+}