Browse Source

Commit at 2024-01-15 00:37.

ZRY 3 months ago
parent
commit
9e09ede413

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="XMakeProjectSettings">
+    <option name="currentArchitecture" value="x86" />
+    <option name="workingDirectory" value="$PROJECT_DIR$" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/ran-proc.iml" filepath="$PROJECT_DIR$/.idea/ran-proc.iml" />
+    </modules>
+  </component>
+</project>

+ 13 - 0
.idea/ran-proc.iml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/pkgtest/pmtest-client/pmtest-client/obj/Debug/net6.0-windows/refint" />
+      <excludeFolder url="file://$MODULE_DIR$/pkgtest/pmtest-client/pmtest-client/bin" />
+      <excludeFolder url="file://$MODULE_DIR$/pkgtest/pmtest-client/pmtest-client/obj" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 8 - 0
go.work

@@ -0,0 +1,8 @@
+go 1.19
+
+use (
+	./pkgtest
+	./rpcore
+	./wslogdist
+	./httppm
+)

+ 16 - 0
go.work.sum

@@ -0,0 +1,16 @@
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 87 - 0
httppm/client.go

@@ -0,0 +1,87 @@
+package httppm
+
+import (
+	"context"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"net/http"
+	"os"
+	"strconv"
+	"time"
+)
+
+type HPMClientLogFunc func(msg string)
+
+type HPMClient struct {
+	hpmURL             string
+	checkAliveDuration time.Duration
+	running            bool
+	closeCncl          context.CancelFunc
+	logFunc            HPMClientLogFunc
+}
+
+func NewHPMClient(hpmURL string, checkAliveDuration time.Duration) *HPMClient {
+	c := &HPMClient{
+		hpmURL:             hpmURL,
+		checkAliveDuration: checkAliveDuration,
+		running:            false,
+		logFunc:            func(msg string) {},
+	}
+	return c
+}
+
+func (c *HPMClient) SetLogFunc(fn HPMClientLogFunc) {
+	c.logFunc = fn
+}
+
+func (c *HPMClient) Run() error {
+	pid := os.Getpid()
+	hdr := http.Header{}
+	hdr.Add("pid", strconv.Itoa(pid))
+	wsconn, _, err := websocket.DefaultDialer.Dial(c.hpmURL, hdr)
+	if err != nil {
+		return fmt.Errorf("HPM Client WS Dial Error: %v", err)
+	}
+	c.logFunc("hpm client websocket connected.")
+	hbTimer := time.NewTicker(c.checkAliveDuration)
+	ctx, cncl := context.WithCancel(context.Background())
+	c.closeCncl = cncl
+	c.running = true
+	defer hbTimer.Stop()
+	go func() {
+		for {
+			_, _, rerr := wsconn.ReadMessage()
+			if rerr != nil {
+				c.logFunc(fmt.Sprint("hpm stop by ws recv fail, detail: ", rerr))
+				_ = wsconn.Close()
+				cncl()
+				return
+			}
+		}
+	}()
+MLoop:
+	for {
+		select {
+		case <-hbTimer.C:
+			xerr := wsconn.WriteMessage(websocket.PingMessage, nil)
+			if xerr != nil {
+				c.logFunc(fmt.Sprint("hpm stop by ws send fail, detail: ", xerr))
+				cncl()
+				continue MLoop
+			}
+		case <-ctx.Done():
+			{
+				c.logFunc("hpm stopped.")
+				return nil
+			}
+		}
+	}
+	return nil
+}
+
+func (c *HPMClient) Stop(xerr error) {
+	if c.running {
+		c.logFunc("hpm stop by other.")
+		c.closeCncl()
+	}
+}

+ 37 - 0
httppm/go.mod

@@ -0,0 +1,37 @@
+module git.swzry.com/zry/ran-proc/httppm
+
+go 1.20
+
+require (
+	github.com/gin-gonic/gin v1.9.1
+	github.com/gorilla/websocket v1.5.0
+	github.com/hashicorp/go-uuid v1.0.3
+	github.com/tjfoc/gmsm v1.4.1
+)
+
+require (
+	github.com/bytedance/sonic v1.9.1 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.14.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.11 // indirect
+	golang.org/x/arch v0.3.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	google.golang.org/protobuf v1.30.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 159 - 0
httppm/go.sum

@@ -0,0 +1,159 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+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/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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+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.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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+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/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/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.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.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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+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/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-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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+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-20190423024810-112230192c58/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+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/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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/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/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.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+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=
+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=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 178 - 0
httppm/server.go

@@ -0,0 +1,178 @@
+package httppm
+
+import (
+	"context"
+	"fmt"
+	"git.swzry.com/zry/GoHiedaLogger/hiedalog"
+	"git.swzry.com/zry/ran-proc/rpcore"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"net/http"
+	"time"
+)
+
+type ServerHandlerConfig struct {
+	GinGrp                *gin.RouterGroup
+	BaseURL               string
+	TimeoutStopFunc       StopFunc
+	Logger                *hiedalog.HiedaLogger
+	LogModuleName         string
+	WebsocketPingDuration time.Duration
+}
+
+func NewServerHandler(cfg *ServerHandlerConfig) *HPMServerHandler {
+	wsup := &websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+		EnableCompression: false,
+	}
+	h := &HPMServerHandler{
+		ginGrp:             cfg.GinGrp,
+		baseURL:            cfg.BaseURL,
+		processMapByAuthID: map[string]*ProcessInfoItem{},
+		processMapByCPID:   map[int64]*ProcessInfoItem{},
+		stopFunc:           cfg.TimeoutStopFunc,
+		logger:             cfg.Logger,
+		logModuleName:      cfg.LogModuleName,
+		upgrader:           wsup,
+		wsPingDur:          cfg.WebsocketPingDuration,
+	}
+	h.ginGrp.GET("/attach.nagae", h.whAttach)
+	return h
+}
+
+type StopFunc func(cpid int64)
+
+type NewProcessConfig struct {
+	AuthID         string
+	ConnectTimeout time.Duration
+}
+
+type ProcessInfoItem struct {
+	CPID       int64
+	AuthID     string
+	chAttached chan int
+	isAttached bool
+	wsCancel   context.CancelFunc
+}
+
+type HPMServerHandler struct {
+	ginGrp             *gin.RouterGroup
+	baseURL            string
+	processMapByAuthID map[string]*ProcessInfoItem
+	processMapByCPID   map[int64]*ProcessInfoItem
+	stopFunc           StopFunc
+	logger             *hiedalog.HiedaLogger
+	logModuleName      string
+	upgrader           *websocket.Upgrader
+	wsPingDur          time.Duration
+}
+
+func (h *HPMServerHandler) whAttach(ctx *gin.Context) {
+	authID := ctx.Query("authid")
+	pii, ok := h.processMapByAuthID[authID]
+	if !ok {
+		h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_VERBOSE, "attach authID not found: authID=%d", authID)
+		ctx.JSON(404, &gin.H{
+			"status":  "auth-id-not-found",
+			"err_msg": "authID not found",
+		})
+		return
+	}
+	wsconn, err := h.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
+	if err != nil {
+		h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_VERBOSE, "failed upgrading to websocket (cpid=%d): %v", pii.CPID, err)
+		ctx.JSON(500, gin.H{
+			"err_msg": "failed upgrading to websocket",
+			"status":  "ws-upgrade-error",
+		})
+		return
+	}
+	defer wsconn.Close()
+	ticker := time.NewTicker(h.wsPingDur)
+	defer ticker.Stop()
+	pii.chAttached <- 1
+	wsctx, wscncl := context.WithCancel(context.Background())
+	pii.wsCancel = wscncl
+	pii.isAttached = true
+	go func() {
+		for {
+			_, _, rerr := wsconn.ReadMessage()
+			if rerr != nil {
+				h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_VERBOSE, "websocket read failed (cpid=%d): %v", pii.CPID, rerr)
+				_ = wsconn.Close()
+				wscncl()
+				return
+			}
+		}
+	}()
+	for {
+		select {
+		case <-wsctx.Done():
+			_ = wsconn.Close()
+			return
+		case <-ticker.C:
+			xerr := wsconn.WriteMessage(websocket.PingMessage, nil)
+			if xerr != nil {
+				_ = wsconn.Close()
+				return
+			}
+		}
+	}
+}
+
+func (h *HPMServerHandler) NewProcess(npcfg *NewProcessConfig) (string, error) {
+	pii := &ProcessInfoItem{
+		CPID:       0,
+		AuthID:     npcfg.AuthID,
+		chAttached: make(chan int),
+		isAttached: false,
+	}
+	h.processMapByAuthID[npcfg.AuthID] = pii
+	uri := fmt.Sprintf("%s/attach.nagae?authid=%s", h.baseURL, npcfg.AuthID)
+	go func() {
+		select {
+		case <-pii.chAttached:
+			h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_INFO, "HPM client attached (CPID=%d).", pii.CPID)
+			return
+		case <-time.After(npcfg.ConnectTimeout):
+			h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_WARN, "HPM client attach timeout (CPID=%d).", pii.CPID)
+			if pii.CPID != 0 {
+				h.stopFunc(pii.CPID)
+				delete(h.processMapByCPID, pii.CPID)
+			}
+			delete(h.processMapByAuthID, npcfg.AuthID)
+			return
+		}
+	}()
+	return uri, nil
+}
+
+func (h *HPMServerHandler) UpdateCPID(authID string, CPID int64) error {
+	pii, ok := h.processMapByAuthID[authID]
+	if !ok {
+		return fmt.Errorf("authID not found")
+	}
+	pii.CPID = CPID
+	h.processMapByCPID[CPID] = pii
+	return nil
+}
+
+func (h *HPMServerHandler) Shutdown(info *rpcore.ShutdownInfo, ctx context.Context) bool {
+	pii, ok := h.processMapByCPID[info.CPID]
+	if !ok {
+		h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_WARN, "can not shutdown (CPID=%d): CPID not found.", pii.CPID)
+		return false
+	}
+	if !pii.isAttached {
+		h.logger.LogPrintf(h.logModuleName, hiedalog.DLN_WARN, "can not shutdown (CPID=%d): not attached.", pii.CPID)
+		return false
+	}
+	pii.wsCancel()
+	if pii.CPID != 0 {
+		delete(h.processMapByCPID, pii.CPID)
+	}
+	delete(h.processMapByAuthID, pii.AuthID)
+	return true
+}

+ 8 - 0
rpcore/.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
rpcore/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/rpcore.iml" filepath="$PROJECT_DIR$/.idea/rpcore.iml" />
+    </modules>
+  </component>
+</project>

+ 9 - 0
rpcore/.idea/rpcore.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
rpcore/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
+  </component>
+</project>

+ 151 - 0
rpcore/cp_item.go

@@ -0,0 +1,151 @@
+package rpcore
+
+import (
+	"io"
+	"os"
+	"os/exec"
+	"sync"
+	"syscall"
+	"time"
+)
+
+type CmdInfoClass struct {
+	// Name will pass to exec.Command()
+	Name string
+
+	// Args will pass to exec.Command()
+	Args []string
+
+	// Env specifies the environment of the process.
+	// Each entry is of the form "key=value".
+	// If Env is nil, the new process uses the current process's
+	// environment.
+	// If Env contains duplicate environment keys, only the last
+	// value in the slice for each duplicate key is used.
+	// As a special case on Windows, SYSTEMROOT is always added if
+	// missing and not explicitly set to the empty string.
+	Env []string
+
+	// Dir specifies the working directory of the command.
+	// If Dir is the empty string, Run runs the command in the
+	// calling process's current directory.
+	Dir string
+
+	// Stdin specifies the process's standard input.
+	//
+	// If Stdin is nil, the process reads from the null device (os.DevNull).
+	//
+	// If Stdin is an *os.File, the process's standard input is connected
+	// directly to that file.
+	//
+	// Otherwise, during the execution of the command a separate
+	// goroutine reads from Stdin and delivers that data to the command
+	// over a pipe. In this case, Wait does not complete until the goroutine
+	// stops copying, either because it has reached the end of Stdin
+	// (EOF or a read error), or because writing to the pipe returned an error,
+	// or because a nonzero WaitDelay was set and expired.
+	Stdin io.Reader
+
+	// Stdout and Stderr specify the process's standard output and error.
+	//
+	// If either is nil, Run connects the corresponding file descriptor
+	// to the null device (os.DevNull).
+	//
+	// If either is an *os.File, the corresponding output from the process
+	// is connected directly to that file.
+	//
+	// Otherwise, during the execution of the command a separate goroutine
+	// reads from the process over a pipe and delivers that data to the
+	// corresponding Writer. In this case, Wait does not complete until the
+	// goroutine reaches EOF or encounters an error or a nonzero WaitDelay
+	// expires.
+	//
+	// If Stdout and Stderr are the same writer, and have a type that can
+	// be compared with ==, at most one goroutine at a time will call Write.
+	Stdout io.Writer
+	Stderr io.Writer
+
+	// ExtraFiles specifies additional open files to be inherited by the
+	// new process. It does not include standard input, standard output, or
+	// standard error. If non-nil, entry i becomes file descriptor 3+i.
+	//
+	// ExtraFiles is not supported on Windows.
+	ExtraFiles []*os.File
+
+	// SysProcAttr holds optional, operating system-specific attributes.
+	// Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
+	SysProcAttr *syscall.SysProcAttr
+
+	// If WaitDelay is non-zero, it bounds the time spent waiting on two sources
+	// of unexpected delay in Wait: a child process that fails to exit after the
+	// associated Context is canceled, and a child process that exits but leaves
+	// its I/O pipes unclosed.
+	//
+	// The WaitDelay timer starts when either the associated Context is done or a
+	// call to Wait observes that the child process has exited, whichever occurs
+	// first. When the delay has elapsed, the command shuts down the child process
+	// and/or its I/O pipes.
+	//
+	// If the child process has failed to exit — perhaps because it ignored or
+	// failed to receive a shutdown signal from a Cancel function, or because no
+	// Cancel function was set — then it will be terminated using os.Process.Kill.
+	//
+	// Then, if the I/O pipes communicating with the child process are still open,
+	// those pipes are closed in order to unblock any goroutines currently blocked
+	// on Read or Write calls.
+	//
+	// If pipes are closed due to WaitDelay, no Cancel call has occurred,
+	// and the command has otherwise exited with a successful status, Wait and
+	// similar methods will return ErrWaitDelay instead of nil.
+	//
+	// If WaitDelay is zero (the default), I/O pipes will be read until EOF,
+	// which might not occur until orphaned subprocesses of the command have
+	// also closed their descriptors for the pipes.
+	WaitDelay time.Duration
+}
+
+type ChildProcTableItem struct {
+	ID               int64
+	Name             string
+	ProcessID        int
+	IsDaemon         bool
+	Status           ProcStatus
+	CmdInfo          *CmdInfoClass
+	Cmd              *exec.Cmd
+	LastStartTime    time.Time
+	Enabled          bool
+	TaskDoneCallback TaskDoneCallbackFunc
+	TaskExecTimeout  time.Duration
+	ShutdownTimeout  time.Duration
+	ShutdownActor    GracefulShutdownActor
+	sync.Mutex
+}
+
+func GetExecCmdFromCmdInfo(cmdInfo *CmdInfoClass) *exec.Cmd {
+	c := exec.Command(cmdInfo.Name, cmdInfo.Args...)
+	if cmdInfo.Env != nil && len(cmdInfo.Env) > 0 {
+		c.Env = cmdInfo.Env
+	}
+	if cmdInfo.Dir != "" {
+		c.Dir = cmdInfo.Dir
+	}
+	if cmdInfo.Stdin != nil {
+		c.Stdin = cmdInfo.Stdin
+	}
+	if cmdInfo.Stdout != nil {
+		c.Stdout = cmdInfo.Stdout
+	}
+	if cmdInfo.Stderr != nil {
+		c.Stderr = cmdInfo.Stderr
+	}
+	if cmdInfo.ExtraFiles != nil && len(cmdInfo.ExtraFiles) > 0 {
+		c.ExtraFiles = cmdInfo.ExtraFiles
+	}
+	if cmdInfo.SysProcAttr != nil {
+		c.SysProcAttr = cmdInfo.SysProcAttr
+	}
+	if cmdInfo.WaitDelay > 0 {
+		c.WaitDelay = cmdInfo.WaitDelay
+	}
+	return c
+}

+ 254 - 0
rpcore/cpmgr.go

@@ -0,0 +1,254 @@
+package rpcore
+
+import (
+	"context"
+	"fmt"
+	"github.com/GUAIK-ORG/go-snowflake/snowflake"
+	"github.com/oklog/run"
+	"sync"
+	"time"
+)
+
+type ChildProcManager struct {
+	cpt            *ChildProcTable
+	snowflaker     *snowflake.Snowflake
+	mainCtx        context.Context
+	mainCancelFunc context.CancelFunc
+	isMgrRunning   bool
+	mainRunGroup   *run.Group
+	msgHdl         ChildProcManagerMsgHandler
+	mainMsgLoop    *MainMsgLoop
+	freezed        bool
+}
+
+func NewChildProcManager(msgHandler ChildProcManagerMsgHandler) *ChildProcManager {
+	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
+	if err != nil {
+		panic(fmt.Errorf("failed init ran-proc ChildProcManager: snowflake error: %v", err))
+	}
+	cpm := &ChildProcManager{
+		cpt:            NewChildProcTable(),
+		snowflaker:     flake,
+		mainCtx:        nil,
+		mainCancelFunc: nil,
+		isMgrRunning:   false,
+		msgHdl:         msgHandler,
+		mainMsgLoop:    nil,
+		freezed:        true,
+	}
+	if cpm.msgHdl == nil {
+		cpm.msgHdl = &DefaultChildProcManagerMsgHandler{}
+	}
+	return cpm
+}
+
+func (m *ChildProcManager) CreateTask(cfg *NewTaskConfig) (cpid int64, rerr error) {
+	cpid = m.snowflaker.NextVal()
+	cpti := &ChildProcTableItem{
+		ID:               cpid,
+		Name:             cfg.Name,
+		ProcessID:        0,
+		IsDaemon:         false,
+		Status:           PROCSTAT_PendingToRun,
+		CmdInfo:          cfg.CmdInfo,
+		Cmd:              nil,
+		LastStartTime:    time.Time{},
+		Enabled:          true,
+		TaskDoneCallback: cfg.DoneCallback,
+		TaskExecTimeout:  cfg.ExecTimeout,
+		ShutdownTimeout:  cfg.ShutdownTimeout,
+		ShutdownActor:    cfg.ShutdownActor,
+		Mutex:            sync.Mutex{},
+	}
+	err := m.createProc(cpti)
+	if err != nil {
+		return -1, fmt.Errorf("failed create task: %v", err)
+	}
+	return cpid, nil
+}
+
+func (m *ChildProcManager) CreateDaemon(cfg *NewDaemonConfig) (cpid int64, rerr error) {
+	cpid = m.snowflaker.NextVal()
+	cpti := &ChildProcTableItem{
+		ID:               cpid,
+		Name:             cfg.Name,
+		ProcessID:        0,
+		IsDaemon:         true,
+		Status:           PROCSTAT_ManuallyStopped,
+		CmdInfo:          cfg.CmdInfo,
+		Cmd:              nil,
+		LastStartTime:    time.Time{},
+		Enabled:          cfg.EnableAfterCreate,
+		TaskDoneCallback: nil,
+		TaskExecTimeout:  0,
+		ShutdownTimeout:  cfg.ShutdownTimeout,
+		ShutdownActor:    cfg.ShutdownActor,
+		Mutex:            sync.Mutex{},
+	}
+	err := m.createProc(cpti)
+	if err != nil {
+		return -1, fmt.Errorf("failed create daemon: %v", err)
+	}
+	return cpid, nil
+}
+
+func (m *ChildProcManager) EnableDaemonByID(CPID int64) error {
+	if m.freezed {
+		return fmt.Errorf("ChildProcManager is not running or freezed for stopping, can not modify any status")
+	}
+	inst, err := m.cpt.GetByID(CPID)
+	if err != nil {
+		return err
+	}
+	inst.Lock()
+	inst.Enabled = true
+	inst.Unlock()
+	go func() {
+		m.mainMsgLoop.AddMsg(&Msg{CPID: inst.ID})
+	}()
+	return nil
+}
+
+func (m *ChildProcManager) DisableDaemonByID(CPID int64) error {
+	if m.freezed {
+		return fmt.Errorf("ChildProcManager is not running or freezed for stopping, can not modify any status")
+	}
+	inst, err := m.cpt.GetByID(CPID)
+	if err != nil {
+		return err
+	}
+	inst.Lock()
+	inst.Enabled = false
+	inst.Unlock()
+	go func() {
+		m.mainMsgLoop.AddMsg(&Msg{CPID: inst.ID})
+	}()
+	return nil
+}
+
+func (m *ChildProcManager) ForceKill(CPID int64) error {
+	inst, err := m.cpt.GetByID(CPID)
+	if err != nil {
+		return err
+	}
+	inst.Lock()
+	err = inst.Cmd.Process.Kill()
+	inst.Unlock()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m *ChildProcManager) GetStatusByID(CPID int64) (*DisplayStatusInfo, error) {
+	inst, err := m.cpt.GetByID(CPID)
+	if err != nil {
+		return nil, err
+	}
+	return &DisplayStatusInfo{
+		CPID:          inst.ID,
+		Name:          inst.Name,
+		Status:        inst.Status,
+		StatusText:    inst.Status.ToString(),
+		StatusAbbr:    inst.Status.ToStringAbbr(),
+		PID:           inst.ProcessID,
+		LastStartTime: inst.LastStartTime,
+	}, nil
+}
+
+func (m *ChildProcManager) ListAllStatus() ([]*DisplayStatusInfo, error) {
+	all, err := m.cpt.ListAll()
+	if err != nil {
+		return nil, err
+	}
+	ret := make([]*DisplayStatusInfo, len(all))
+	for i, v := range all {
+		ret[i] = &DisplayStatusInfo{
+			CPID:          v.ID,
+			Name:          v.Name,
+			Status:        v.Status,
+			StatusText:    v.Status.ToString(),
+			StatusAbbr:    v.Status.ToStringAbbr(),
+			PID:           v.ProcessID,
+			LastStartTime: v.LastStartTime,
+		}
+	}
+	return ret, nil
+}
+
+func (m *ChildProcManager) QueryStatus(index string, args ...interface{}) ([]*DisplayStatusInfo, error) {
+	all, err := m.cpt.DoQuery(index, args...)
+	if err != nil {
+		return nil, err
+	}
+	ret := make([]*DisplayStatusInfo, len(all))
+	for i, v := range all {
+		ret[i] = &DisplayStatusInfo{
+			CPID:          v.ID,
+			Name:          v.Name,
+			Status:        v.Status,
+			StatusText:    v.Status.ToString(),
+			StatusAbbr:    v.Status.ToStringAbbr(),
+			PID:           v.ProcessID,
+			LastStartTime: v.LastStartTime,
+		}
+	}
+	return ret, nil
+}
+
+func (m *ChildProcManager) createProc(cpti *ChildProcTableItem) error {
+	if m.freezed {
+		return fmt.Errorf("ChildProcManager is not running or freezed for stopping, can not modify any status")
+	}
+	err := m.cpt.Add(cpti)
+	if err != nil {
+		return err
+	}
+	go func() {
+		m.mainMsgLoop.AddMsg(&Msg{CPID: cpti.ID})
+	}()
+	return nil
+}
+
+func (m *ChildProcManager) Start() chan error {
+	cherr := make(chan error, 1)
+	if m.isMgrRunning {
+		cherr <- fmt.Errorf("ChildProcManager is already running")
+	}
+	ctx, cncl := context.WithCancel(context.Background())
+	m.mainCtx = ctx
+	m.mainCancelFunc = cncl
+	m.mainMsgLoop = NewMainMsgLoop(m, ctx)
+	m.mainRunGroup = &run.Group{}
+	m.mainRunGroup.Add(m.mainMsgLoop.RunMsgLoop, m.mainMsgLoop.StopMainLoop)
+	m.mainRunGroup.Add(m.mainMsgLoop.RunInspectionLoop, m.mainMsgLoop.StopInspectionLoop)
+	go func() {
+		m.isMgrRunning = true
+		m.freezed = false
+		err := m.mainRunGroup.Run()
+		m.mainCtx = nil
+		m.mainCancelFunc = nil
+		m.mainMsgLoop = nil
+		m.mainRunGroup = nil
+		m.isMgrRunning = false
+		cherr <- err
+	}()
+	return cherr
+}
+
+func (m *ChildProcManager) Stop() error {
+	if m.isMgrRunning {
+		if m.mainCancelFunc == nil {
+			return fmt.Errorf("ChildProcManager internal error: cancel func is nil")
+		}
+		m.freezed = true
+		m.mainCancelFunc()
+	} else {
+		return fmt.Errorf("ChildProcManager not running")
+	}
+	return nil
+}
+
+func (m *ChildProcManager) IsRunning() bool {
+	return m.isMgrRunning
+}

+ 151 - 0
rpcore/cptable.go

@@ -0,0 +1,151 @@
+package rpcore
+
+import (
+	"fmt"
+	memdb "github.com/hashicorp/go-memdb"
+)
+
+const CPTABLE_NAME = "cpt"
+
+type ChildProcTable struct {
+	db *memdb.MemDB
+}
+
+func NewChildProcTable() *ChildProcTable {
+	scheme := &memdb.DBSchema{
+		Tables: map[string]*memdb.TableSchema{
+			CPTABLE_NAME: &memdb.TableSchema{
+				Name: CPTABLE_NAME,
+				Indexes: map[string]*memdb.IndexSchema{
+					"id": &memdb.IndexSchema{
+						Name:         "id",
+						AllowMissing: false,
+						Unique:       true,
+						Indexer:      &memdb.IntFieldIndex{Field: "ID"},
+					},
+					"name": &memdb.IndexSchema{
+						Name:         "name",
+						AllowMissing: false,
+						Unique:       false,
+						Indexer:      &memdb.StringFieldIndex{Field: "Name"},
+					},
+					"is_daemon": &memdb.IndexSchema{
+						Name:         "is_daemon",
+						AllowMissing: false,
+						Unique:       false,
+						Indexer:      &memdb.BoolFieldIndex{Field: "IsDaemon"},
+					},
+					"status": &memdb.IndexSchema{
+						Name:         "status",
+						AllowMissing: false,
+						Unique:       false,
+						Indexer:      &memdb.IntFieldIndex{Field: "Status"},
+					},
+					"pid": &memdb.IndexSchema{
+						Name:         "pid",
+						AllowMissing: false,
+						Unique:       false,
+						Indexer:      &memdb.IntFieldIndex{Field: "ProcessID"},
+					},
+				},
+			},
+		},
+	}
+	mdb, err := memdb.NewMemDB(scheme)
+	if err != nil {
+		panic(fmt.Errorf("failed init ChildProcTable: memdb error: %s", err))
+	}
+	t := &ChildProcTable{
+		db: mdb,
+	}
+	return t
+}
+
+func (c *ChildProcTable) Add(item *ChildProcTableItem) error {
+	txn := c.db.Txn(true)
+	err := txn.Insert(CPTABLE_NAME, item)
+	if err != nil {
+		return fmt.Errorf("add cpitem failed: set: %s", err)
+	}
+	txn.Commit()
+	return nil
+}
+
+func (c *ChildProcTable) GetByID(id int64) (*ChildProcTableItem, error) {
+	txn := c.db.Txn(false)
+	defer txn.Abort()
+	raw, err := txn.First(CPTABLE_NAME, "id", id)
+	if err != nil {
+		return nil, fmt.Errorf("get cpitem failed: %s", err)
+	}
+	if raw == nil {
+		return nil, fmt.Errorf("get cpitem failed: no such item")
+	}
+	return raw.(*ChildProcTableItem), nil
+}
+
+func (c *ChildProcTable) GetByName(name string) ([]*ChildProcTableItem, error) {
+	txn := c.db.Txn(false)
+	defer txn.Abort()
+	it, err := txn.Get(CPTABLE_NAME, "name", name)
+	if err != nil {
+		return nil, fmt.Errorf("get cpitem failed: %s", err)
+	}
+	out := make([]*ChildProcTableItem, 0)
+	for obj := it.Next(); obj != nil; obj = it.Next() {
+		out = append(out, obj.(*ChildProcTableItem))
+	}
+	return out, nil
+}
+
+func (c *ChildProcTable) Delete(item *ChildProcTableItem) error {
+	txn := c.db.Txn(true)
+	err := txn.Delete(CPTABLE_NAME, item)
+	if err != nil {
+		return fmt.Errorf("delete cpitem failed: %s", err)
+	}
+	txn.Commit()
+	return nil
+}
+
+func (c *ChildProcTable) GetByPID(pid int) ([]*ChildProcTableItem, error) {
+	txn := c.db.Txn(false)
+	defer txn.Abort()
+	it, err := txn.Get(CPTABLE_NAME, "pid", pid)
+	if err != nil {
+		return nil, fmt.Errorf("get cpitem failed: %s", err)
+	}
+	out := make([]*ChildProcTableItem, 0)
+	for obj := it.Next(); obj != nil; obj = it.Next() {
+		out = append(out, obj.(*ChildProcTableItem))
+	}
+	return out, nil
+}
+
+func (c *ChildProcTable) ListAll() ([]*ChildProcTableItem, error) {
+	txn := c.db.Txn(false)
+	defer txn.Abort()
+	it, err := txn.Get(CPTABLE_NAME, "id")
+	if err != nil {
+		return nil, fmt.Errorf("get items failed: %s", err)
+	}
+	out := make([]*ChildProcTableItem, 0)
+	for obj := it.Next(); obj != nil; obj = it.Next() {
+		out = append(out, obj.(*ChildProcTableItem))
+	}
+	return out, nil
+}
+
+func (c *ChildProcTable) DoQuery(index string, args ...interface{}) ([]*ChildProcTableItem, error) {
+	txn := c.db.Txn(false)
+	defer txn.Abort()
+	it, err := txn.Get(CPTABLE_NAME, index, args...)
+	if err != nil {
+		return nil, fmt.Errorf("query items failed: %s", err)
+	}
+	out := make([]*ChildProcTableItem, 0)
+	for obj := it.Next(); obj != nil; obj = it.Next() {
+		out = append(out, obj.(*ChildProcTableItem))
+	}
+	return out, nil
+}

+ 55 - 0
rpcore/ext_intf.go

@@ -0,0 +1,55 @@
+package rpcore
+
+import (
+	"git.swzry.com/zry/GoHiedaLogger/hiedalog"
+)
+
+type ChildProcManagerMsgHandler interface {
+	ManagerStart()
+	ManagerStop(err error)
+	MsgLoopVerboseLog(CPID int64, text string)
+	ProcessStarted(CPID int64)
+	ProcessQuit(CPID int64, err error)
+}
+
+type DefaultChildProcManagerMsgHandler struct {
+	Logger        *hiedalog.HiedaLogger
+	LogModuleName string
+}
+
+func (d DefaultChildProcManagerMsgHandler) ManagerStart() {
+	if d.Logger == nil {
+		return
+	}
+	d.Logger.LogPrint(d.LogModuleName, hiedalog.DLN_INFO, "<procman> start running")
+}
+
+func (d DefaultChildProcManagerMsgHandler) ManagerStop(err error) {
+	if d.Logger == nil {
+		return
+	}
+	if err != nil {
+		d.Logger.LogPrint(d.LogModuleName, hiedalog.DLN_INFO, "<procman> stopped with error: ", err)
+	} else {
+		d.Logger.LogPrint(d.LogModuleName, hiedalog.DLN_INFO, "<procman> stopped normally")
+	}
+}
+
+func (d DefaultChildProcManagerMsgHandler) MsgLoopVerboseLog(CPID int64, text string) {
+	if d.Logger == nil {
+		return
+	}
+	d.Logger.LogPrintf(d.LogModuleName, hiedalog.DLN_VERBOSE, "<msgloop> | %16x | %s", CPID, text)
+}
+
+func (d DefaultChildProcManagerMsgHandler) ProcessQuit(CPID int64, err error) {
+	if err == nil {
+		d.Logger.LogPrintf(d.LogModuleName, hiedalog.DLN_INFO, "<procend> | %16x | end normally", CPID)
+	} else {
+		d.Logger.LogPrintf(d.LogModuleName, hiedalog.DLN_WARN, "<procend> | %16x | end with error: %v", CPID, err)
+	}
+}
+
+func (d DefaultChildProcManagerMsgHandler) ProcessStarted(CPID int64) {
+	d.Logger.LogPrintf(d.LogModuleName, hiedalog.DLN_INFO, "<procend> | %16x | started", CPID)
+}

+ 17 - 0
rpcore/go.mod

@@ -0,0 +1,17 @@
+module git.swzry.com/zry/ran-proc/rpcore
+
+go 1.19
+
+require (
+	git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20221201004124-970fc945a7b7
+	github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f
+	github.com/hashicorp/go-memdb v1.3.4
+	github.com/oklog/run v1.1.0
+	github.com/smallnest/chanx v1.1.0
+)
+
+require (
+	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
+	github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
+	github.com/hashicorp/golang-lru v0.5.4 // indirect
+)

+ 23 - 0
rpcore/go.sum

@@ -0,0 +1,23 @@
+git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20221201004124-970fc945a7b7 h1:EkaA9qf1XK4g7//o0cQTCc+zvz1fdvEXPDQWIvd7EoI=
+git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20221201004124-970fc945a7b7/go.mod h1:NMU7558kNXCUuK0qKYQMtYK/kn2lhwelnij295H3pdU=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
+github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c=
+github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg=
+github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+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/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
+github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/smallnest/chanx v1.1.0 h1:2f/anv7jUuFqeOvCRy4ApmVixWcYUz1ya0vpVx6P7vQ=
+github.com/smallnest/chanx v1.1.0/go.mod h1:zXoLoNTSzdRiD+XNNsfAj6/jY7ZfBnviL96tVr+tQ3E=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

+ 25 - 0
rpcore/graceful_shutdown_intf.go

@@ -0,0 +1,25 @@
+package rpcore
+
+import (
+	"context"
+	"os/exec"
+)
+
+type ShutdownInfo struct {
+	CPID        int64
+	Name        string
+	PID         int
+	Cmd         *exec.Cmd
+	LogEmitFunc func(text string)
+}
+
+type GracefulShutdownActor interface {
+	Shutdown(info *ShutdownInfo, ctx context.Context) bool
+}
+
+type DummyShutdownActor struct {
+}
+
+func (d DummyShutdownActor) Shutdown(info *ShutdownInfo, ctx context.Context) bool {
+	return false
+}

+ 346 - 0
rpcore/main_msg_loop.go

@@ -0,0 +1,346 @@
+package rpcore
+
+import (
+	"context"
+	"fmt"
+	"github.com/smallnest/chanx"
+	"time"
+)
+
+const InitialQueueCapacity = 10
+const InspectionInterval = 10 * time.Second
+const RestartWaitTime = 3 * time.Second
+
+type MainMsgLoop struct {
+	mgr          *ChildProcManager
+	msgLoopCtx   context.Context
+	msgLoopCncl  context.CancelFunc
+	msgQueue     *chanx.UnboundedChan[*Msg]
+	inspLoopCtx  context.Context
+	inspLoopCncl context.CancelFunc
+}
+
+func NewMainMsgLoop(p *ChildProcManager, pctx context.Context) *MainMsgLoop {
+	mlctx, mlcncl := context.WithCancel(pctx)
+	ilctx, ilcncl := context.WithCancel(pctx)
+	return &MainMsgLoop{
+		mgr:          p,
+		msgLoopCtx:   mlctx,
+		msgLoopCncl:  mlcncl,
+		msgQueue:     chanx.NewUnboundedChan[*Msg](InitialQueueCapacity),
+		inspLoopCtx:  ilctx,
+		inspLoopCncl: ilcncl,
+	}
+}
+
+func (l *MainMsgLoop) AddMsg(msg *Msg) {
+	l.msgQueue.In <- msg
+}
+
+func (l *MainMsgLoop) RunInspectionLoop() error {
+LabelInspLoop:
+	for {
+		select {
+		case <-l.inspLoopCtx.Done():
+			{
+				break LabelInspLoop
+			}
+		case <-time.After(InspectionInterval):
+			{
+				allinst, err := l.mgr.cpt.ListAll()
+				if err == nil {
+					for _, v := range allinst {
+						l.AddMsg(&Msg{CPID: v.ID})
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func (l *MainMsgLoop) RunMsgLoop() error {
+LabelMainMsgLoop:
+	for {
+		select {
+		case <-l.msgLoopCtx.Done():
+			{
+				break LabelMainMsgLoop
+			}
+		case m := <-l.msgQueue.Out:
+			{
+				l.doMsg(m)
+			}
+		}
+	}
+	if !l.mgr.freezed {
+		l.mgr.freezed = true
+	}
+	spmlCtx, spmlCncl := context.WithCancel(context.Background())
+	spmlDoneCh := make(chan int, 0)
+	go l.doStopProcedureMsgLoop(spmlCtx, spmlDoneCh)
+LabelCheckStopProcedureOkLoop:
+	for {
+		allinst, err := l.mgr.cpt.ListAll()
+		if err == nil {
+			if len(allinst) <= 0 {
+				break LabelCheckStopProcedureOkLoop
+			}
+			for _, v := range allinst {
+				v.Enabled = false
+				v.Status = PROCSTAT_PendingToStop
+				go l.AddMsg(&Msg{CPID: v.ID})
+			}
+		} else {
+			break LabelCheckStopProcedureOkLoop
+		}
+		time.Sleep(RestartWaitTime)
+	}
+	spmlCncl()
+	<-spmlDoneCh
+	return nil
+}
+
+func (l *MainMsgLoop) doKillProcess(inst *ChildProcTableItem) {
+	if inst.Cmd == nil {
+		return
+	}
+	p := inst.Cmd.Process
+	if p == nil {
+		return
+	}
+	err := p.Kill()
+	if err != nil {
+		l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("error in kill process: %v", err))
+		go func() {
+			time.Sleep(RestartWaitTime)
+			l.AddMsg(&Msg{CPID: inst.ID})
+		}()
+	} else {
+		inst.Status = PROCSTAT_Exited
+		go func() {
+			l.AddMsg(&Msg{CPID: inst.ID})
+		}()
+	}
+}
+
+func (l *MainMsgLoop) doStopProcedureMsgLoop(ctx context.Context, doneCh chan int) {
+LabelStopProcedureMsgLoop:
+	for {
+		select {
+		case <-ctx.Done():
+			{
+				break LabelStopProcedureMsgLoop
+			}
+		case m := <-l.msgQueue.Out:
+			{
+				l.doMsg(m)
+			}
+		}
+	}
+	doneCh <- 0
+}
+
+func (l *MainMsgLoop) doMsg(msg *Msg) {
+	inst, err := l.mgr.cpt.GetByID(msg.CPID)
+	if err != nil || inst == nil {
+		return
+	}
+	defer inst.Unlock()
+	inst.Lock()
+	switch inst.Status {
+	case PROCSTAT_PendingToDelete:
+		{
+			err = l.mgr.cpt.Delete(inst)
+			if err != nil {
+				l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("delete: error: %s", err))
+			} else {
+				l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("delete: ok"))
+			}
+			break
+		}
+	case PROCSTAT_Exited:
+		{
+			if inst.IsDaemon {
+				if l.mgr.freezed {
+					inst.Status = PROCSTAT_PendingToDelete
+					go func() {
+						l.AddMsg(&Msg{CPID: msg.CPID})
+					}()
+				} else {
+					if inst.Enabled {
+						inst.Status = PROCSTAT_PendingToRun
+						inst.Cmd = nil
+						l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("pending to restart"))
+						go func() {
+							time.Sleep(RestartWaitTime)
+							l.AddMsg(&Msg{CPID: msg.CPID})
+						}()
+					} else {
+						inst.Status = PROCSTAT_ManuallyStopped
+					}
+				}
+				/*				if inst.Enabled {
+									inst.Status = PROCSTAT_PendingToRun
+									l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("pending to restart"))
+									go func() {
+										time.Sleep(RestartWaitTime)
+										l.AddMsg(&Msg{CPID: msg.CPID})
+									}()
+								} else {
+									inst.Status = PROCSTAT_ManuallyStopped
+								}
+				*/
+			} else {
+				if inst.TaskDoneCallback != nil {
+					go inst.TaskDoneCallback(inst.ID, inst.Name)
+				}
+				inst.Status = PROCSTAT_PendingToDelete
+				l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("pending to delete"))
+				go func() {
+					l.AddMsg(&Msg{CPID: msg.CPID})
+				}()
+			}
+			break
+		}
+	case PROCSTAT_Running:
+		{
+			if inst.IsDaemon {
+				if !inst.Enabled {
+					inst.Status = PROCSTAT_PendingToStop
+					go func() {
+						l.AddMsg(&Msg{CPID: msg.CPID})
+					}()
+				}
+			} else {
+				if inst.TaskExecTimeout > 0 && (!inst.LastStartTime.IsZero()) {
+					if time.Now().After(inst.LastStartTime.Add(inst.TaskExecTimeout)) {
+						inst.Status = PROCSTAT_PendingToStop
+						go func() {
+							l.AddMsg(&Msg{CPID: msg.CPID})
+						}()
+					}
+				}
+			}
+			break
+		}
+	case PROCSTAT_ManuallyStopped:
+		{
+			if inst.IsDaemon {
+				if inst.Enabled {
+					inst.Status = PROCSTAT_PendingToRun
+					go func() {
+						l.AddMsg(&Msg{CPID: msg.CPID})
+					}()
+				}
+			}
+			break
+		}
+	case PROCSTAT_PendingToRun:
+		{
+			if inst.CmdInfo == nil {
+				l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("no command to start, pending to delete"))
+				inst.Status = PROCSTAT_PendingToDelete
+				go func() {
+					l.AddMsg(&Msg{CPID: msg.CPID})
+				}()
+			} else {
+				if inst.Cmd != nil {
+					l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("seemed already running, pending to stop for restarting..."))
+					inst.Status = PROCSTAT_PendingToStop
+					go func() {
+						l.AddMsg(&Msg{CPID: msg.CPID})
+					}()
+				} else {
+					inst.Cmd = GetExecCmdFromCmdInfo(inst.CmdInfo)
+					err = inst.Cmd.Start()
+					if err != nil {
+						l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("start failed: %v", err))
+						inst.Status = PROCSTAT_Exited
+						go func() {
+							l.AddMsg(&Msg{CPID: msg.CPID})
+						}()
+					} else {
+						inst.Status = PROCSTAT_Running
+						inst.LastStartTime = time.Now()
+						inst.ProcessID = inst.Cmd.Process.Pid
+						go func() {
+							l.mgr.msgHdl.ProcessStarted(inst.ID)
+							err = inst.Cmd.Wait()
+							inst.Status = PROCSTAT_Exited
+							l.mgr.msgHdl.ProcessQuit(inst.ID, err)
+							l.AddMsg(&Msg{CPID: msg.CPID})
+						}()
+					}
+				}
+			}
+			break
+		}
+	case PROCSTAT_PendingToStop:
+		{
+			if inst.Cmd == nil {
+				l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("no command to stop"))
+				inst.Status = PROCSTAT_Exited
+				go func() {
+					l.AddMsg(&Msg{CPID: msg.CPID})
+				}()
+			} else {
+				p := inst.Cmd.Process
+				if p == nil {
+					l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprintf("no command to stop"))
+					inst.Status = PROCSTAT_Exited
+					go func() {
+						l.AddMsg(&Msg{CPID: msg.CPID})
+					}()
+				} else {
+					if inst.ShutdownActor == nil {
+						l.doKillProcess(inst)
+					} else {
+						gsactx, gsacncl := context.WithCancel(context.Background())
+						donech := make(chan int, 0)
+						go func() {
+							ok := inst.ShutdownActor.Shutdown(&ShutdownInfo{
+								CPID: inst.ID,
+								Name: inst.Name,
+								PID:  inst.ProcessID,
+								Cmd:  inst.Cmd,
+								LogEmitFunc: func(text string) {
+									l.mgr.msgHdl.MsgLoopVerboseLog(inst.ID, fmt.Sprint("graceful shutdown actor: ", text))
+								},
+							}, gsactx)
+							if !ok {
+								l.doKillProcess(inst)
+							}
+							close(donech)
+						}()
+						if inst.ShutdownTimeout > 0 {
+							go func() {
+								select {
+								case <-donech:
+									return
+								case <-time.After(inst.TaskExecTimeout):
+									gsacncl()
+									l.doKillProcess(inst)
+									return
+								}
+							}()
+						}
+					}
+				}
+			}
+			break
+		}
+	}
+}
+
+func (l *MainMsgLoop) StopMainLoop(err error) {
+	if l.msgLoopCncl != nil {
+		l.msgLoopCncl()
+	}
+}
+
+func (l *MainMsgLoop) StopInspectionLoop(err error) {
+	if l.inspLoopCncl != nil {
+		l.inspLoopCncl()
+	}
+}

+ 5 - 0
rpcore/msg.go

@@ -0,0 +1,5 @@
+package rpcore
+
+type Msg struct {
+	CPID int64
+}

+ 24 - 0
rpcore/proc_cfg.go

@@ -0,0 +1,24 @@
+package rpcore
+
+import (
+	"time"
+)
+
+type TaskDoneCallbackFunc func(cpid int64, name string)
+
+type NewTaskConfig struct {
+	Name            string
+	CmdInfo         *CmdInfoClass
+	DoneCallback    TaskDoneCallbackFunc
+	ExecTimeout     time.Duration
+	ShutdownTimeout time.Duration
+	ShutdownActor   GracefulShutdownActor
+}
+
+type NewDaemonConfig struct {
+	Name              string
+	CmdInfo           *CmdInfoClass
+	EnableAfterCreate bool
+	ShutdownTimeout   time.Duration
+	ShutdownActor     GracefulShutdownActor
+}

+ 84 - 0
rpcore/status.go

@@ -0,0 +1,84 @@
+package rpcore
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+)
+
+type ProcStatus int8
+
+const (
+	PROCSTAT_PendingToRun    ProcStatus = iota
+	PROCSTAT_Running         ProcStatus = iota
+	PROCSTAT_Exited          ProcStatus = iota
+	PROCSTAT_PendingToStop   ProcStatus = iota
+	PROCSTAT_PendingToDelete ProcStatus = iota
+	PROCSTAT_ManuallyStopped
+)
+
+func (ps ProcStatus) ToString() string {
+	switch ps {
+	case PROCSTAT_PendingToRun:
+		return "PendingR"
+	case PROCSTAT_Running:
+		return "Running"
+	case PROCSTAT_Exited:
+		return "Exited"
+	case PROCSTAT_PendingToStop:
+		return "PendingS"
+	case PROCSTAT_ManuallyStopped:
+		return "Stopped"
+	case PROCSTAT_PendingToDelete:
+		return "PendingD"
+	default:
+		return "Unknown"
+	}
+}
+
+func (ps ProcStatus) ToStringAbbr() string {
+	switch ps {
+	case PROCSTAT_PendingToRun:
+		return "PDR"
+	case PROCSTAT_Running:
+		return "RUN"
+	case PROCSTAT_Exited:
+		return "END"
+	case PROCSTAT_PendingToStop:
+		return "PDS"
+	case PROCSTAT_ManuallyStopped:
+		return "OFF"
+	case PROCSTAT_PendingToDelete:
+		return "PDD"
+	default:
+		return "UKN"
+	}
+}
+
+type DisplayStatusInfo struct {
+	CPID          int64      `json:"cpid"`
+	Name          string     `json:"name"`
+	Status        ProcStatus `json:"status"`
+	StatusText    string     `json:"statusText"`
+	StatusAbbr    string     `json:"statusAbbr"`
+	PID           int        `json:"pid"`
+	LastStartTime time.Time  `json:"lastStartTime"`
+}
+
+func (i DisplayStatusInfo) ToPrintableTableData() []string {
+	tmstr := "-"
+	if !i.LastStartTime.IsZero() {
+		tmstr = i.LastStartTime.Format(time.RFC3339Nano)
+	}
+	pid := "-"
+	if i.PID > 0 {
+		pid = strconv.Itoa(i.PID)
+	}
+	return []string{
+		fmt.Sprintf("%16X", i.CPID),
+		i.Name,
+		i.StatusAbbr,
+		pid,
+		tmstr,
+	}
+}

+ 30 - 0
rpcore/unix_shutdown_actor_unix.go

@@ -0,0 +1,30 @@
+//go:build unix
+
+package rpcore
+
+import (
+	"context"
+	"syscall"
+)
+
+type UnixSignalShutdownActor struct {
+}
+
+func (u UnixSignalShutdownActor) Shutdown(info *ShutdownInfo, ctx context.Context) bool {
+	if info == nil {
+		return false
+	}
+	if info.Cmd == nil {
+		return false
+	}
+	if info.Cmd.Process == nil {
+		return false
+	}
+	err = info.Cmd.Process.Signal(syscall.SIGINT)
+	if err != nil {
+		info.LogEmitFunc(fmt.Sprintf("error in sending SIGINT to process (CPID=%16X, PID=%d): %v", info.CPID, info.PID, err))
+		return false
+	} else {
+		return true
+	}
+}

+ 12 - 0
rpcore/unix_shutdown_actor_windows.go

@@ -0,0 +1,12 @@
+//go:build windows
+
+package rpcore
+
+import "context"
+
+type UnixSignalShutdownActor struct {
+}
+
+func (u UnixSignalShutdownActor) Shutdown(info *ShutdownInfo, ctx context.Context) bool {
+	return false
+}

+ 37 - 0
wslogdist/go.mod

@@ -0,0 +1,37 @@
+module git.swzry.com/zry/ran-proc/wslogdist
+
+go 1.19
+
+require (
+	git.swzry.com/zry/gorillaws2netconn v0.0.0-20210320151634-09bb0427fd87
+	github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f
+	github.com/gin-gonic/gin v1.9.0
+	github.com/gorilla/websocket v1.5.0
+)
+
+require (
+	github.com/bytedance/sonic v1.8.0 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.11.2 // indirect
+	github.com/goccy/go-json v0.10.0 // indirect
+	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	github.com/leodido/go-urn v1.2.1 // indirect
+	github.com/mattn/go-isatty v0.0.17 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.9 // indirect
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+	golang.org/x/crypto v0.5.0 // indirect
+	golang.org/x/net v0.7.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 89 - 0
wslogdist/go.sum

@@ -0,0 +1,89 @@
+git.swzry.com/zry/gorillaws2netconn v0.0.0-20210320151634-09bb0427fd87 h1:eFrvcQKQsMWDK5M+7DUc+0jzspWRaDCWzasc54CSbZc=
+git.swzry.com/zry/gorillaws2netconn v0.0.0-20210320151634-09bb0427fd87/go.mod h1:LPDkF7hyn8Vmh6aLJVIT+mY6GBNKGcW99Tpj/8Xu7k8=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
+github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
+github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
+github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+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/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
+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/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/stretchr/objx v0.1.0/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
+github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+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=

+ 139 - 0
wslogdist/ws_log_distr.go

@@ -0,0 +1,139 @@
+package wslogdist
+
+import (
+	"bytes"
+	"fmt"
+	"git.swzry.com/zry/gorillaws2netconn"
+	"github.com/GUAIK-ORG/go-snowflake/snowflake"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"net/http"
+	"sync"
+	"time"
+)
+
+type WsSessions struct {
+	Conn      *gorillaws2netconn.NetConn4Gorilla
+	BeginTime time.Time
+}
+
+func NewWsSessions(conn *gorillaws2netconn.NetConn4Gorilla) *WsSessions {
+	s := &WsSessions{
+		Conn:      conn,
+		BeginTime: time.Now(),
+	}
+	return s
+}
+
+type WsLogDistributor struct {
+	wsList             map[string]*WsSessions
+	lock               sync.RWMutex
+	upgrader           *websocket.Upgrader
+	startLogBuffer     *bytes.Buffer
+	startLogBufferSize int
+	snowFlaker         *snowflake.Snowflake
+}
+
+func NewWsLogDistributor(startLogBufferSize int) *WsLogDistributor {
+	d := &WsLogDistributor{
+		wsList:             make(map[string]*WsSessions),
+		startLogBufferSize: startLogBufferSize,
+	}
+	d.upgrader = &websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	bbuf := make([]byte, 0, startLogBufferSize)
+	d.startLogBuffer = bytes.NewBuffer(bbuf)
+	flake, err := snowflake.NewSnowflake(int64(0), int64(0))
+	if err != nil {
+		panic(err)
+	}
+	d.snowFlaker = flake
+	return d
+}
+
+func (w *WsLogDistributor) Write(p []byte) (n int, err error) {
+	if w.startLogBuffer.Len() < w.startLogBufferSize {
+		w.startLogBuffer.Write(p)
+	}
+	defer w.lock.Unlock()
+	w.lock.Lock()
+	for k, v := range w.wsList {
+		_, err := v.Conn.Write(p)
+		if err != nil {
+			_ = v.Conn.WS.Close()
+			delete(w.wsList, k)
+		}
+	}
+	return len(p), nil
+}
+
+func (w *WsLogDistributor) HandleNewConnections(ctx *gin.Context) {
+	ustr := fmt.Sprintf("%16X", w.snowFlaker.NextVal())
+	ws, err := w.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
+	if err != nil {
+		ctx.JSON(500, gin.H{
+			"suc":     false,
+			"err_msg": "failed upgrading to websocket",
+		})
+		return
+	}
+	nconn := &gorillaws2netconn.NetConn4Gorilla{WS: ws, WriteMessageType: websocket.TextMessage}
+	w.lock.Lock()
+	sess := NewWsSessions(nconn)
+	w.wsList[ustr] = sess
+	w.lock.Unlock()
+	buf := make([]byte, 1024)
+	kc := make(chan int)
+	go func() {
+		for {
+			_, err := sess.Conn.Read(buf)
+			if err != nil {
+				kc <- 0
+				return
+			}
+		}
+	}()
+	<-kc
+	_ = sess.Conn.Close()
+	w.lock.Lock()
+	delete(w.wsList, ustr)
+	w.lock.Unlock()
+}
+
+func (w *WsLogDistributor) ClearConnections() {
+	defer w.lock.Unlock()
+	w.lock.Lock()
+	for k, v := range w.wsList {
+		_ = v.Conn.Close()
+		delete(w.wsList, k)
+	}
+}
+
+func (w *WsLogDistributor) GetClientsInfo() map[string]map[string]interface{} {
+	defer w.lock.RUnlock()
+	w.lock.RLock()
+	now := time.Now()
+	m := map[string]map[string]interface{}{}
+	for k, v := range w.wsList {
+		m[k] = map[string]interface{}{
+			"begin_time":               v.BeginTime.Unix(),
+			"begin_time_in_rfc3399":    v.BeginTime.Format(time.RFC3339),
+			"begin_to_now_nanoseconds": now.Sub(v.BeginTime),
+			"begin_to_now_str":         now.Sub(v.BeginTime).String(),
+			"remote_addr":              v.Conn.RemoteAddr().String(),
+			"local_addr":               v.Conn.LocalAddr().String(),
+		}
+	}
+	return m
+}
+
+func (w *WsLogDistributor) GetStartLog() string {
+	return w.startLogBuffer.String()
+}
+
+func (w *WsLogDistributor) ClearStartLog() {
+	w.startLogBuffer.Reset()
+}