| @@ -0,0 +1,19 @@ | |||
| # Binaries for programs and plugins | |||
| *.exe | |||
| *.exe~ | |||
| *.dll | |||
| *.so | |||
| *.dylib | |||
| # Test binary, built with `go test -c` | |||
| *.test | |||
| # Output of the go coverage tool, specifically when used with LiteIDE | |||
| *.out | |||
| # Dependency directories (remove the comment below to include it) | |||
| # vendor/ | |||
| .idea/ | |||
| vendor/ | |||
| root.data | |||
| root.data.1 | |||
| @@ -0,0 +1,13 @@ | |||
| # seata-golang | |||
| ### 一个朴素的想法 | |||
| 作为一个刚入 Golang 坑的微服务普通开发者来讲,很容易产生一个朴素的想法,希望 Golang 微服务也有分布式事务解决方案。我们注意到阿里开源了 Java 版的分布式事务解决方案 Seata,本项目尝试将 Java 版的 Seata 改写一个 Golang 的版本。 | |||
| 在 Seata 没有 Golang 版本 client sdk 的情况下,Golang 版本的 TC Server 使用了和 Java 版 Seata 一样的通信协议,方便调试。 | |||
| 目前,基于内存的 TC Server 已实现。希望有同样朴素想法的开发者加入我们一起完善 Golang 版本的分布式事务解决方案。 | |||
| ### todo list | |||
| - [X] Memory Session Manager | |||
| - [ ] DB Session Manager | |||
| - [ ] ETCD Session Manager | |||
| - [ ] TC | |||
| - [ ] RM | |||
| @@ -0,0 +1,26 @@ | |||
| package common | |||
| import ( | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| type XId struct { | |||
| Port int | |||
| IpAddress string | |||
| } | |||
| var XID = &XId{} | |||
| func (xId *XId) GenerateXID(tranId int64) string { | |||
| return fmt.Sprintf("%s:%d:%d",xId.IpAddress,xId.Port,tranId) | |||
| } | |||
| func (xId *XId) GetTransactionId(xid string) int64 { | |||
| if xid == "" { return -1 } | |||
| idx := strings.LastIndex(xid,":") | |||
| tranId,_ := strconv.ParseInt(xid[idx:],10,64) | |||
| return tranId | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| module github.com/dk-lockdown/seata-golang | |||
| go 1.13.3 | |||
| require ( | |||
| github.com/dubbogo/getty v1.3.3 | |||
| github.com/dubbogo/gost v1.6.0 | |||
| github.com/gorilla/websocket v1.4.1 // indirect | |||
| github.com/pkg/errors v0.9.1 | |||
| github.com/stretchr/testify v1.5.1 | |||
| go.uber.org/atomic v1.5.0 | |||
| go.uber.org/zap v1.14.0 // indirect | |||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | |||
| gopkg.in/yaml.v2 v2.2.8 | |||
| vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 | |||
| vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d // indirect | |||
| ) | |||
| @@ -0,0 +1,84 @@ | |||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | |||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | |||
| github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |||
| 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/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= | |||
| github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= | |||
| github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= | |||
| github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= | |||
| github.com/dubbogo/gost v1.6.0 h1:30ERJDdVUcfpc/oFtqPspazQ5dHfyRbDQ2QZvAFxaUE= | |||
| github.com/dubbogo/gost v1.6.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= | |||
| github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= | |||
| github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | |||
| github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= | |||
| github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= | |||
| github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | |||
| github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= | |||
| github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | |||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | |||
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | |||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||
| github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | |||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |||
| 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | |||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |||
| 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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= | |||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | |||
| go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= | |||
| go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | |||
| go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= | |||
| go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= | |||
| go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= | |||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | |||
| go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= | |||
| go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= | |||
| go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= | |||
| go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= | |||
| go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= | |||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | |||
| go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o= | |||
| go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= | |||
| 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/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= | |||
| golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | |||
| golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= | |||
| 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |||
| golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |||
| golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= | |||
| golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
| golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= | |||
| golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | |||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
| 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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | |||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | |||
| gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | |||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |||
| gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= | |||
| gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |||
| honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= | |||
| honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= | |||
| vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 h1:pxt6fVJP67Hxo1qk8JalUghLlk3abYByl+3e0JYfUlE= | |||
| vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10/go.mod h1:fl9OF22g6MTKgvHA1hqMXe/L7+ULWofVTwbC9loGu7A= | |||
| vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d h1:Mp6WiHHuiwHaknxTdxJ8pvC9/B4pOgW1PamKGexG7Fs= | |||
| vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d/go.mod h1:zHGDKp2tyvF4IAfLti4pKYqCJucXYmmKMb3UMrCHK/4= | |||
| @@ -0,0 +1,186 @@ | |||
| package logging | |||
| import ( | |||
| "fmt" | |||
| "log" | |||
| "os" | |||
| ) | |||
| // Level represents the level of logging. | |||
| type LogLevel uint8 | |||
| const ( | |||
| Debug LogLevel = iota | |||
| Info | |||
| Warn | |||
| Error | |||
| Fatal | |||
| Panic | |||
| ) | |||
| type ILogger interface { | |||
| Debug(v ...interface{}) | |||
| Debugf(format string, v ...interface{}) | |||
| Info(v ...interface{}) | |||
| Infof(format string, v ...interface{}) | |||
| Warn(v ...interface{}) | |||
| Warnf(format string, v ...interface{}) | |||
| Error(v ...interface{}) | |||
| Errorf(format string, v ...interface{}) | |||
| Fatal(v ...interface{}) | |||
| Fatalf(format string, v ...interface{}) | |||
| Panic(v ...interface{}) | |||
| Panicf(format string, v ...interface{}) | |||
| } | |||
| const ( | |||
| DefaultLogLevel = Info | |||
| DefaultNamespace = "default" | |||
| ) | |||
| type SeataLogger struct { | |||
| loggers []*log.Logger | |||
| namespace string | |||
| logLevel LogLevel | |||
| } | |||
| var Logger *SeataLogger | |||
| func init() { | |||
| var loggers = make([]*log.Logger, 0) | |||
| loggers = append(loggers, log.New(os.Stdout, "", log.LstdFlags)) | |||
| Logger = &SeataLogger{ | |||
| loggers: loggers, | |||
| namespace: DefaultNamespace, | |||
| logLevel: DefaultLogLevel, | |||
| } | |||
| } | |||
| func merge(namespace, logLevel, msg string) string { | |||
| return fmt.Sprintf("%s %s %s", namespace, logLevel, msg) | |||
| } | |||
| func SetNamespace(namespace string) { | |||
| Logger.namespace = namespace | |||
| } | |||
| func SetLogLevel(logLevel LogLevel) { | |||
| Logger.logLevel = logLevel | |||
| } | |||
| func AddLogger(logger *log.Logger) { | |||
| Logger.loggers = append(Logger.loggers, logger) | |||
| } | |||
| func (l *SeataLogger) Debug(v ...interface{}) { | |||
| if Debug < l.logLevel || len(v) == 0 { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "DEBUG", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Debugf(format string, v ...interface{}) { | |||
| if Debug < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "DEBUG", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Info(v ...interface{}) { | |||
| if Info < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "INFO", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Infof(format string, v ...interface{}) { | |||
| if Info < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "INFO", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Warn(v ...interface{}) { | |||
| if Warn < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "WARNING", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Warnf(format string, v ...interface{}) { | |||
| if Warn < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "WARNING", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Error(v ...interface{}) { | |||
| if Error < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "ERROR", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Errorf(format string, v ...interface{}) { | |||
| if Error < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "ERROR", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Fatal(v ...interface{}) { | |||
| if Fatal < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "FATAL", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Fatalf(format string, v ...interface{}) { | |||
| if Fatal < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "FATAL", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Panic(v ...interface{}) { | |||
| if Panic < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "PANIC", fmt.Sprint(v...))) | |||
| } | |||
| } | |||
| func (l *SeataLogger) Panicf(format string, v ...interface{}) { | |||
| if Panic < l.logLevel { | |||
| return | |||
| } | |||
| for _,log := range l.loggers { | |||
| log.Print(merge(l.namespace, "PANIC", fmt.Sprintf(format, v...))) | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| package logging | |||
| import "testing" | |||
| func TestSeataLogger_Info(t *testing.T) { | |||
| Logger.Info("there is a bug") | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| package meta | |||
| import "fmt" | |||
| type BranchStatus byte | |||
| const ( | |||
| /** | |||
| * The BranchStatus_Unknown. | |||
| * description:BranchStatus_Unknown branch status. | |||
| */ | |||
| BranchStatusUnknown BranchStatus = iota | |||
| /** | |||
| * The BranchStatus_Registered. | |||
| * description:BranchStatus_Registered to TC. | |||
| */ | |||
| BranchStatusRegistered | |||
| /** | |||
| * The Phase one done. | |||
| * description:Branch logic is successfully done at phase one. | |||
| */ | |||
| BranchStatusPhaseoneDone | |||
| /** | |||
| * The Phase one failed. | |||
| * description:Branch logic is failed at phase one. | |||
| */ | |||
| BranchStatusPhaseoneFailed | |||
| /** | |||
| * The Phase one timeout. | |||
| * description:Branch logic is NOT reported for a timeout. | |||
| */ | |||
| BranchStatusPhaseoneTimeout | |||
| /** | |||
| * The Phase two committed. | |||
| * description:Commit logic is successfully done at phase two. | |||
| */ | |||
| BranchStatusPhasetwoCommitted | |||
| /** | |||
| * The Phase two commit failed retryable. | |||
| * description:Commit logic is failed but retryable. | |||
| */ | |||
| BranchStatusPhasetwoCommitFailedRetryable | |||
| /** | |||
| * The Phase two commit failed unretryable. | |||
| * description:Commit logic is failed and NOT retryable. | |||
| */ | |||
| BranchStatusPhasetwoCommitFailedUnretryable | |||
| /** | |||
| * The Phase two rollbacked. | |||
| * description:Rollback logic is successfully done at phase two. | |||
| */ | |||
| BranchStatusPhasetwoRollbacked | |||
| /** | |||
| * The Phase two rollback failed retryable. | |||
| * description:Rollback logic is failed but retryable. | |||
| */ | |||
| BranchStatusPhasetwoRollbackFailedRetryable | |||
| /** | |||
| * The Phase two rollback failed unretryable. | |||
| * description:Rollback logic is failed but NOT retryable. | |||
| */ | |||
| BranchStatusPhasetwoRollbackFailedUnretryable | |||
| ) | |||
| func (s BranchStatus) String() string { | |||
| switch s { | |||
| case BranchStatusUnknown: | |||
| return "Unknown" | |||
| case BranchStatusRegistered: | |||
| return "Registered" | |||
| case BranchStatusPhaseoneDone: | |||
| return "PhaseoneDone" | |||
| case BranchStatusPhaseoneFailed: | |||
| return "PhaseoneFailed" | |||
| case BranchStatusPhaseoneTimeout: | |||
| return "PhaseoneTimeout" | |||
| case BranchStatusPhasetwoCommitted: | |||
| return "PhasetwoCommitted" | |||
| case BranchStatusPhasetwoCommitFailedRetryable: | |||
| return "PhasetwoCommitFailedRetryable" | |||
| case BranchStatusPhasetwoCommitFailedUnretryable: | |||
| return "CommitFailedUnretryable" | |||
| case BranchStatusPhasetwoRollbacked: | |||
| return "PhasetwoRollbacked" | |||
| case BranchStatusPhasetwoRollbackFailedRetryable: | |||
| return "RollbackFailedRetryable" | |||
| case BranchStatusPhasetwoRollbackFailedUnretryable: | |||
| return "RollbackFailedUnretryable" | |||
| default: | |||
| return fmt.Sprintf("%d", s) | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package meta | |||
| import "fmt" | |||
| type BranchType byte | |||
| const ( | |||
| /** | |||
| * The At. | |||
| */ | |||
| // BranchType_AT Branch | |||
| BranchTypeAT BranchType = iota | |||
| /** | |||
| * The BranchType_TCC. | |||
| */ | |||
| BranchTypeTCC | |||
| /** | |||
| * The BranchType_SAGA. | |||
| */ | |||
| BranchTypeSAGA | |||
| ) | |||
| func (t BranchType) String() string { | |||
| switch t { | |||
| case BranchTypeAT: | |||
| return "AT" | |||
| case BranchTypeTCC: | |||
| return "TCC" | |||
| case BranchTypeSAGA: | |||
| return "SAGA" | |||
| default: | |||
| return fmt.Sprintf("%d", t) | |||
| } | |||
| } | |||
| @@ -0,0 +1,142 @@ | |||
| package meta | |||
| import "fmt" | |||
| type GlobalStatus int32 | |||
| const ( | |||
| /** | |||
| * Un known global status. | |||
| */ | |||
| // BranchStatus_Unknown | |||
| GlobalStatusUnknown GlobalStatus = iota | |||
| /** | |||
| * The GlobalStatus_Begin. | |||
| */ | |||
| // PHASE 1: can accept new branch registering. | |||
| GlobalStatusBegin | |||
| /** | |||
| * PHASE 2: Running Status: may be changed any time. | |||
| */ | |||
| // Committing. | |||
| GlobalStatusCommitting | |||
| /** | |||
| * The Commit retrying. | |||
| */ | |||
| // Retrying commit after a recoverable failure. | |||
| GlobalStatusCommitRetrying | |||
| /** | |||
| * Rollbacking global status. | |||
| */ | |||
| // Rollbacking | |||
| GlobalStatusRollbacking | |||
| /** | |||
| * The Rollback retrying. | |||
| */ | |||
| // Retrying rollback after a recoverable failure. | |||
| GlobalStatusRollbackRetrying | |||
| /** | |||
| * The Timeout rollbacking. | |||
| */ | |||
| // Rollbacking since timeout | |||
| GlobalStatusTimeoutRollbacking | |||
| /** | |||
| * The Timeout rollback retrying. | |||
| */ | |||
| // Retrying rollback (since timeout) after a recoverable failure. | |||
| GlobalStatusTimeoutRollbackRetrying | |||
| /** | |||
| * All branches can be async committed. The committing is NOT done yet, but it can be seen as committed for TM/RM | |||
| * client. | |||
| */ | |||
| GlobalStatusAsyncCommitting | |||
| /** | |||
| * PHASE 2: Final Status: will NOT change any more. | |||
| */ | |||
| // Finally: global transaction is successfully committed. | |||
| GlobalStatusCommitted | |||
| /** | |||
| * The Commit failed. | |||
| */ | |||
| // Finally: failed to commit | |||
| GlobalStatusCommitFailed | |||
| /** | |||
| * The Rollbacked. | |||
| */ | |||
| // Finally: global transaction is successfully rollbacked. | |||
| GlobalStatusRollbacked | |||
| /** | |||
| * The Rollback failed. | |||
| */ | |||
| // Finally: failed to rollback | |||
| GlobalStatusRollbackFailed | |||
| /** | |||
| * The Timeout rollbacked. | |||
| */ | |||
| // Finally: global transaction is successfully rollbacked since timeout. | |||
| GlobalStatusTimeoutRollbacked | |||
| /** | |||
| * The Timeout rollback failed. | |||
| */ | |||
| // Finally: failed to rollback since timeout | |||
| GlobalStatusTimeoutRollbackFailed | |||
| /** | |||
| * The Finished. | |||
| */ | |||
| // Not managed in session MAP any more | |||
| GlobalStatusFinished | |||
| ) | |||
| func (s GlobalStatus) String() string { | |||
| switch s { | |||
| case GlobalStatusUnknown: | |||
| return "Unknown" | |||
| case GlobalStatusBegin: | |||
| return "Begin" | |||
| case GlobalStatusCommitting: | |||
| return "Committing" | |||
| case GlobalStatusCommitRetrying: | |||
| return "CommitRetrying" | |||
| case GlobalStatusRollbacking: | |||
| return "Rollbacking" | |||
| case GlobalStatusRollbackRetrying: | |||
| return "RollbackRetrying" | |||
| case GlobalStatusTimeoutRollbacking: | |||
| return "TimeoutRollbacking" | |||
| case GlobalStatusTimeoutRollbackRetrying: | |||
| return "TimeoutRollbackRetrying" | |||
| case GlobalStatusAsyncCommitting: | |||
| return "AsyncCommitting" | |||
| case GlobalStatusCommitted: | |||
| return "Committed" | |||
| case GlobalStatusCommitFailed: | |||
| return "CommitFailed" | |||
| case GlobalStatusRollbacked: | |||
| return "Rollbacked" | |||
| case GlobalStatusRollbackFailed: | |||
| return "RollbackFailed" | |||
| case GlobalStatusTimeoutRollbacked: | |||
| return "TimeoutRollbacked" | |||
| case GlobalStatusTimeoutRollbackFailed: | |||
| return "TimeoutRollbackFailed" | |||
| case GlobalStatusFinished: | |||
| return "Finished" | |||
| default: | |||
| return fmt.Sprintf("%d", s) | |||
| } | |||
| } | |||
| @@ -0,0 +1,110 @@ | |||
| package meta | |||
| type TransactionExceptionCode byte | |||
| const ( | |||
| /** | |||
| * Unknown transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeUnknown TransactionExceptionCode = iota | |||
| /** | |||
| * BeginFailed | |||
| */ | |||
| TransactionExceptionCodeBeginFailed | |||
| /** | |||
| * Lock key conflict transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeLockKeyConflict | |||
| /** | |||
| * Io transaction exception code. | |||
| */ | |||
| IO | |||
| /** | |||
| * Branch rollback failed retriable transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeBranchRollbackFailedRetriable | |||
| /** | |||
| * Branch rollback failed unretriable transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeBranchRollbackFailedUnretriable | |||
| /** | |||
| * Branch register failed transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeBranchRegisterFailed | |||
| /** | |||
| * Branch report failed transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeBranchReportFailed | |||
| /** | |||
| * Lockable check failed transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeLockableCheckFailed | |||
| /** | |||
| * Branch transaction not exist transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeBranchTransactionNotExist | |||
| /** | |||
| * Global transaction not exist transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeGlobalTransactionNotExist | |||
| /** | |||
| * Global transaction not active transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeGlobalTransactionNotActive | |||
| /** | |||
| * Global transaction status invalid transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeGlobalTransactionStatusInvalid | |||
| /** | |||
| * Failed to send branch commit request transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeFailedToSendBranchCommitRequest | |||
| /** | |||
| * Failed to send branch rollback request transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeFailedToSendBranchRollbackRequest | |||
| /** | |||
| * Failed to add branch transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeFailedToAddBranch | |||
| /** | |||
| * Failed to lock global transaction exception code. | |||
| */ | |||
| TransactionExceptionCodeFailedLockGlobalTranscation | |||
| /** | |||
| * FailedWriteSession | |||
| */ | |||
| TransactionExceptionCodeFailedWriteSession | |||
| /** | |||
| * Failed to holder exception code | |||
| */ | |||
| FailedStore | |||
| ) | |||
| type TransactionException struct { | |||
| Code TransactionExceptionCode | |||
| Message string | |||
| } | |||
| //Error 隐式继承 builtin.error 接口 | |||
| func (e TransactionException) Error() string { | |||
| return "TransactionException: " + e.Message | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| package meta | |||
| import "fmt" | |||
| type TransactionRole byte | |||
| const ( | |||
| /** | |||
| * tm | |||
| */ | |||
| TMROLE TransactionRole = iota | |||
| /** | |||
| * rm | |||
| */ | |||
| RMROLE | |||
| /** | |||
| * server | |||
| */ | |||
| SERVERROLE | |||
| ) | |||
| func (r TransactionRole) String() string { | |||
| switch r { | |||
| case TMROLE: | |||
| return "TMROLE" | |||
| case RMROLE: | |||
| return "RMROLE" | |||
| case SERVERROLE: | |||
| return "SERVERROLE" | |||
| default: | |||
| return fmt.Sprintf("%d", r) | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| package model | |||
| import "github.com/dk-lockdown/seata-golang/meta" | |||
| type IResource interface { | |||
| /** | |||
| * Get the resource group id. | |||
| * e.g. master and slave data-source should be with the same resource group id. | |||
| * | |||
| * @return resource group id. | |||
| */ | |||
| getResourceGroupId() string | |||
| /** | |||
| * Get the resource id. | |||
| * e.g. url of a data-source could be the id of the db data-source resource. | |||
| * | |||
| * @return resource id. | |||
| */ | |||
| getResourceId() string | |||
| /** | |||
| * get resource type, BranchType_AT, BranchType_TCC, BranchType_SAGA and XA | |||
| * | |||
| * @return | |||
| */ | |||
| getBranchType() meta.BranchType | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| package model | |||
| import "sync" | |||
| type Set struct { | |||
| m map[string]bool | |||
| sync.RWMutex | |||
| } | |||
| func NewSet() *Set { | |||
| return &Set{ | |||
| m: make(map[string]bool), | |||
| } | |||
| } | |||
| // Add add | |||
| func (s *Set) Add(item string) { | |||
| s.Lock() | |||
| defer s.Unlock() | |||
| s.m[item] = true | |||
| } | |||
| // Remove deletes the specified item from the map | |||
| func (s *Set) Remove(item string) { | |||
| s.Lock() | |||
| defer s.Unlock() | |||
| delete(s.m, item) | |||
| } | |||
| // Has looks for the existence of an item | |||
| func (s *Set) Has(item string) bool { | |||
| s.RLock() | |||
| defer s.RUnlock() | |||
| _, ok := s.m[item] | |||
| return ok | |||
| } | |||
| // Len returns the number of items in a set. | |||
| func (s *Set) Len() int { | |||
| return len(s.List()) | |||
| } | |||
| // Clear removes all items from the set | |||
| func (s *Set) Clear() { | |||
| s.Lock() | |||
| defer s.Unlock() | |||
| s.m = make(map[string]bool) | |||
| } | |||
| // IsEmpty checks for emptiness | |||
| func (s *Set) IsEmpty() bool { | |||
| if s.Len() == 0 { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| // Set returns a slice of all items | |||
| func (s *Set) List() []string { | |||
| s.RLock() | |||
| defer s.RUnlock() | |||
| list := make([]string, 0) | |||
| for item := range s.m { | |||
| list = append(list, item) | |||
| } | |||
| return list | |||
| } | |||
| @@ -0,0 +1,243 @@ | |||
| package codec | |||
| import ( | |||
| "bytes" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| type SerializerType byte | |||
| const ( | |||
| SEATA = byte(0x1) | |||
| PROTOBUF = byte(0x2) | |||
| KRYO = byte(0x4) | |||
| FST = byte(0x8) | |||
| ) | |||
| type Encoder func(in interface{}) []byte | |||
| type Decoder func(in []byte) (interface{},int) | |||
| func MessageEncoder(codecType byte,in interface{}) []byte { | |||
| switch codecType { | |||
| case SEATA: | |||
| return SeataEncoder(in) | |||
| default: | |||
| logging.Logger.Errorf("not support codecType, %s",codecType) | |||
| return nil | |||
| } | |||
| } | |||
| func MessageDecoder(codecType byte,in []byte) (interface{},int) { | |||
| switch codecType { | |||
| case SEATA: | |||
| return SeataDecoder(in) | |||
| default: | |||
| logging.Logger.Errorf("not support codecType, %s",codecType) | |||
| return nil,0 | |||
| } | |||
| } | |||
| func SeataEncoder(in interface{}) []byte { | |||
| var result = make([]byte, 0) | |||
| msg := in.(protocal.MessageTypeAware) | |||
| typeCode := msg.GetTypeCode() | |||
| encoder := getMessageEncoder(typeCode) | |||
| typeC := uint16(typeCode) | |||
| if encoder != nil { | |||
| body := encoder(in) | |||
| result = append(result,[]byte{ byte(typeC >> 8), byte(typeC) }...) | |||
| result = append(result,body...) | |||
| } | |||
| return result | |||
| } | |||
| func SeataDecoder(in []byte) (interface{},int) { | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| typeCode, _ ,_ := r.ReadInt16() | |||
| decoder := getMessageDecoder(typeCode) | |||
| if decoder != nil { | |||
| return decoder(in[2:]) | |||
| } | |||
| return nil,0 | |||
| } | |||
| func getMessageEncoder(typeCode int16) Encoder { | |||
| switch typeCode { | |||
| case protocal.TypeSeataMerge: | |||
| return MergedWarpMessageEncoder | |||
| case protocal.TypeSeataMergeResult: | |||
| return MergeResultMessageEncoder | |||
| case protocal.TypeRegClt: | |||
| return RegisterTMRequestEncoder | |||
| case protocal.TypeRegCltResult: | |||
| return RegisterTMResponseEncoder | |||
| case protocal.TypeRegRm: | |||
| return RegisterRMRequestEncoder | |||
| case protocal.TypeRegRmResult: | |||
| return RegisterRMResponseEncoder | |||
| case protocal.TypeBranchCommit: | |||
| return BranchCommitRequestEncoder | |||
| case protocal.TypeBranchRollback: | |||
| return BranchRollbackRequestEncoder | |||
| case protocal.TypeGlobalReport: | |||
| return GlobalReportRequestEncoder | |||
| default: | |||
| var encoder Encoder | |||
| encoder = getMergeRequestMessageEncoder(typeCode) | |||
| if encoder != nil { | |||
| return encoder | |||
| } | |||
| encoder = getMergeResponseMessageEncoder(typeCode) | |||
| if encoder != nil { | |||
| return encoder | |||
| } | |||
| logging.Logger.Errorf("not support typeCode, %d",typeCode) | |||
| return nil | |||
| } | |||
| } | |||
| func getMergeRequestMessageEncoder(typeCode int16) Encoder { | |||
| switch typeCode { | |||
| case protocal.TypeGlobalBegin: | |||
| return GlobalBeginRequestEncoder | |||
| case protocal.TypeGlobalCommit: | |||
| return GlobalCommitRequestEncoder | |||
| case protocal.TypeGlobalRollback: | |||
| return GlobalRollbackRequestEncoder | |||
| case protocal.TypeGlobalStatus: | |||
| return GlobalStatusRequestEncoder | |||
| case protocal.TypeGlobalLockQuery: | |||
| return GlobalLockQueryRequestEncoder | |||
| case protocal.TypeBranchRegister: | |||
| return BranchRegisterRequestEncoder | |||
| case protocal.TypeBranchStatusReport: | |||
| return BranchReportRequestEncoder | |||
| case protocal.TypeGlobalReport: | |||
| return GlobalReportRequestEncoder | |||
| default: | |||
| break | |||
| } | |||
| return nil | |||
| } | |||
| func getMergeResponseMessageEncoder(typeCode int16) Encoder { | |||
| switch typeCode { | |||
| case protocal.TypeGlobalBeginResult: | |||
| return GlobalBeginResponseEncoder | |||
| case protocal.TypeGlobalCommitResult: | |||
| return GlobalCommitResponseEncoder | |||
| case protocal.TypeGlobalRollbackResult: | |||
| return GlobalRollbackResponseEncoder | |||
| case protocal.TypeGlobalStatusResult: | |||
| return GlobalStatusResponseEncoder | |||
| case protocal.TypeGlobalLockQueryResult: | |||
| return GlobalLockQueryResponseEncoder | |||
| case protocal.TypeBranchRegisterResult: | |||
| return BranchRegisterResponseEncoder | |||
| case protocal.TypeBranchStatusReportResult: | |||
| return BranchReportResponseEncoder | |||
| case protocal.TypeBranchCommitResult: | |||
| return BranchCommitResponseEncoder | |||
| case protocal.TypeBranchRollbackResult: | |||
| return BranchRollbackResponseEncoder | |||
| case protocal.TypeGlobalReportResult: | |||
| return GlobalReportResponseEncoder | |||
| default: | |||
| break | |||
| } | |||
| return nil | |||
| } | |||
| func getMessageDecoder(typeCode int16) Decoder { | |||
| switch typeCode { | |||
| case protocal.TypeSeataMerge: | |||
| return MergedWarpMessageDecoder | |||
| case protocal.TypeSeataMergeResult: | |||
| return MergeResultMessageDecoder | |||
| case protocal.TypeRegClt: | |||
| return RegisterTMRequestDecoder | |||
| case protocal.TypeRegCltResult: | |||
| return RegisterTMResponseDecoder | |||
| case protocal.TypeRegRm: | |||
| return RegisterRMRequestDecoder | |||
| case protocal.TypeRegRmResult: | |||
| return RegisterRMResponseDecoder | |||
| case protocal.TypeBranchCommit: | |||
| return BranchCommitRequestDecoder | |||
| case protocal.TypeBranchRollback: | |||
| return BranchRollbackRequestDecoder | |||
| case protocal.TypeGlobalReport: | |||
| return GlobalReportRequestDecoder | |||
| default: | |||
| var Decoder Decoder | |||
| Decoder = getMergeRequestMessageDecoder(typeCode) | |||
| if Decoder != nil { | |||
| return Decoder | |||
| } | |||
| Decoder = getMergeResponseMessageDecoder(typeCode) | |||
| if Decoder != nil { | |||
| return Decoder | |||
| } | |||
| logging.Logger.Errorf("not support typeCode, %d",typeCode) | |||
| return nil | |||
| } | |||
| } | |||
| func getMergeRequestMessageDecoder(typeCode int16) Decoder { | |||
| switch typeCode { | |||
| case protocal.TypeGlobalBegin: | |||
| return GlobalBeginRequestDecoder | |||
| case protocal.TypeGlobalCommit: | |||
| return GlobalCommitRequestDecoder | |||
| case protocal.TypeGlobalRollback: | |||
| return GlobalRollbackRequestDecoder | |||
| case protocal.TypeGlobalStatus: | |||
| return GlobalStatusRequestDecoder | |||
| case protocal.TypeGlobalLockQuery: | |||
| return GlobalLockQueryRequestDecoder | |||
| case protocal.TypeBranchRegister: | |||
| return BranchRegisterRequestDecoder | |||
| case protocal.TypeBranchStatusReport: | |||
| return BranchReportRequestDecoder | |||
| case protocal.TypeGlobalReport: | |||
| return GlobalReportRequestDecoder | |||
| default: | |||
| break | |||
| } | |||
| return nil | |||
| } | |||
| func getMergeResponseMessageDecoder(typeCode int16) Decoder { | |||
| switch typeCode { | |||
| case protocal.TypeGlobalBeginResult: | |||
| return GlobalBeginResponseDecoder | |||
| case protocal.TypeGlobalCommitResult: | |||
| return GlobalCommitResponseDecoder | |||
| case protocal.TypeGlobalRollbackResult: | |||
| return GlobalRollbackResponseDecoder | |||
| case protocal.TypeGlobalStatusResult: | |||
| return GlobalStatusResponseDecoder | |||
| case protocal.TypeGlobalLockQueryResult: | |||
| return GlobalLockQueryResponseDecoder | |||
| case protocal.TypeBranchRegisterResult: | |||
| return BranchRegisterResponseDecoder | |||
| case protocal.TypeBranchStatusReportResult: | |||
| return BranchReportResponseDecoder | |||
| case protocal.TypeBranchCommitResult: | |||
| return BranchCommitResponseDecoder | |||
| case protocal.TypeBranchRollbackResult: | |||
| return BranchRollbackResponseDecoder | |||
| case protocal.TypeGlobalReportResult: | |||
| return GlobalReportResponseDecoder | |||
| default: | |||
| break | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,773 @@ | |||
| package codec | |||
| import ( | |||
| "bytes" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| func AbstractResultMessageDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractResultMessage{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| totalReadN += 1 | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func MergedWarpMessageDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| size16 int16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| result := protocal.MergedWarpMessage{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| r.ReadInt32() | |||
| totalReadN += 4 | |||
| size16,readN,_ = r.ReadInt16() | |||
| totalReadN += readN | |||
| result.Msgs = make([]protocal.MessageTypeAware,0) | |||
| for index := 0; index < int(size16); index++ { | |||
| typeCode,_,_ := r.ReadInt16() | |||
| totalReadN += 2 | |||
| decoder := getMessageDecoder(typeCode) | |||
| if decoder != nil { | |||
| msg,readN := decoder(in[totalReadN:]) | |||
| totalReadN += readN | |||
| result.Msgs = append(result.Msgs,msg.(protocal.MessageTypeAware)) | |||
| } | |||
| } | |||
| return result,totalReadN | |||
| } | |||
| func MergeResultMessageDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| size16 int16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| result := protocal.MergeResultMessage{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| r.ReadInt32() | |||
| totalReadN += 4 | |||
| size16,readN,_ = r.ReadInt16() | |||
| totalReadN += readN | |||
| result.Msgs = make([]protocal.MessageTypeAware,0) | |||
| for index := 0; index < int(size16); index++ { | |||
| typeCode,_,_ := r.ReadInt16() | |||
| totalReadN += 2 | |||
| decoder := getMessageDecoder(typeCode) | |||
| if decoder != nil { | |||
| msg,readN := decoder(in[totalReadN:]) | |||
| totalReadN += readN | |||
| result.Msgs = append(result.Msgs,msg.(protocal.MessageTypeAware)) | |||
| } | |||
| } | |||
| return result,totalReadN | |||
| } | |||
| func AbstractIdentifyRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractIdentifyRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Version, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ApplicationId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.TransactionServiceGroup, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ExtraData = make([]byte,int(length16)) | |||
| readN, _ := r.Read(msg.ExtraData) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractIdentifyResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractIdentifyResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| identified, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| if identified == byte(1){ | |||
| msg.Identified = true | |||
| } else if identified == byte(0) { | |||
| msg.Identified = false | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Version, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func RegisterRMRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length32 uint32 = 0 | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.RegisterRMRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Version, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ApplicationId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.TransactionServiceGroup, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ExtraData = make([]byte,int(length16)) | |||
| readN, _ := r.Read(msg.ExtraData) | |||
| totalReadN += readN | |||
| } | |||
| length32, readN, _ = r.ReadUint32() | |||
| totalReadN += readN | |||
| if length32 > 0 { | |||
| msg.ResourceIds, readN, _ = r.ReadString(int(length32)) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func RegisterRMResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractIdentifyResponseDecoder(in) | |||
| abstractIdentifyResponse := resp.(protocal.AbstractIdentifyResponse) | |||
| msg := protocal.RegisterRMResponse{AbstractIdentifyResponse:abstractIdentifyResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func RegisterTMRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractIdentifyRequestDecoder(in) | |||
| abstractIdentifyRequest := req.(protocal.AbstractIdentifyRequest) | |||
| msg := protocal.RegisterTMRequest{AbstractIdentifyRequest:abstractIdentifyRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func RegisterTMResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractIdentifyResponseDecoder(in) | |||
| abstractIdentifyResponse := resp.(protocal.AbstractIdentifyResponse) | |||
| msg := protocal.RegisterRMResponse{AbstractIdentifyResponse:abstractIdentifyResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractTransactionResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractTransactionResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractBranchEndRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractBranchEndRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| msg.BranchId, _, _ = r.ReadInt64() | |||
| totalReadN += 8 | |||
| branchType, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.BranchType = meta.BranchType(branchType) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ResourceId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ApplicationData = make([]byte,int(length16)) | |||
| readN,_ := r.Read(msg.ApplicationData) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractBranchEndResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractBranchEndResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| msg.BranchId, _, _ = r.ReadInt64() | |||
| totalReadN += 8 | |||
| branchStatus,_ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.BranchStatus = meta.BranchStatus(branchStatus) | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractGlobalEndRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractGlobalEndRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ExtraData = make([]byte,int(length16)) | |||
| readN,_ := r.Read(msg.ExtraData) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func AbstractGlobalEndResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.AbstractGlobalEndResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| globalStatus,_ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.GlobalStatus = meta.GlobalStatus(globalStatus) | |||
| return msg,totalReadN | |||
| } | |||
| func BranchCommitRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractBranchEndRequestDecoder(in) | |||
| abstractBranchEndRequest := req.(protocal.AbstractBranchEndRequest) | |||
| msg := protocal.BranchCommitRequest{AbstractBranchEndRequest:abstractBranchEndRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func BranchCommitResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractBranchEndResponseDecoder(in) | |||
| abstractBranchEndResponse := resp.(protocal.AbstractBranchEndResponse) | |||
| msg := protocal.BranchCommitResponse{AbstractBranchEndResponse:abstractBranchEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func BranchRegisterRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length32 uint32 = 0 | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.BranchRegisterRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| branchType, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.BranchType = meta.BranchType(branchType) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ResourceId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length32, readN, _ = r.ReadUint32() | |||
| totalReadN += readN | |||
| if length32 > 0 { | |||
| msg.LockKey, readN, _ = r.ReadString(int(length32)) | |||
| totalReadN += readN | |||
| } | |||
| length32, readN, _ = r.ReadUint32() | |||
| totalReadN += readN | |||
| if length32 > 0 { | |||
| msg.ApplicationData = make([]byte,int(length32)) | |||
| readN,_ := r.Read(msg.ApplicationData) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func BranchRegisterResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.BranchRegisterResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| msg.BranchId, readN, _ = r.ReadInt64() | |||
| totalReadN += readN | |||
| return msg,totalReadN | |||
| } | |||
| func BranchReportRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.BranchReportRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| msg.BranchId, _, _ = r.ReadInt64() | |||
| branchStatus, _ := r.ReadByte() | |||
| msg.Status = meta.BranchStatus(branchStatus) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ResourceId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ApplicationData = make([]byte,int(length16)) | |||
| readN,_ := r.Read(msg.ApplicationData) | |||
| totalReadN += readN | |||
| } | |||
| branchType, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.BranchType = meta.BranchType(branchType) | |||
| return msg,totalReadN | |||
| } | |||
| func BranchReportResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractTransactionResponseDecoder(in) | |||
| abstractTransactionResponse := resp.(protocal.AbstractTransactionResponse) | |||
| msg := protocal.BranchReportResponse{AbstractTransactionResponse: abstractTransactionResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func BranchRollbackRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractBranchEndRequestDecoder(in) | |||
| abstractBranchEndRequest := req.(protocal.AbstractBranchEndRequest) | |||
| msg := protocal.BranchRollbackRequest{AbstractBranchEndRequest:abstractBranchEndRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func BranchRollbackResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractBranchEndResponseDecoder(in) | |||
| abstractBranchEndResponse := resp.(protocal.AbstractBranchEndResponse) | |||
| msg := protocal.BranchRollbackResponse{AbstractBranchEndResponse:abstractBranchEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalBeginRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.GlobalBeginRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| timeout, readN, _ := r.ReadInt32() | |||
| totalReadN += readN | |||
| msg.Timeout = timeout | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.TransactionName, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalBeginResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.GlobalBeginResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ExtraData = make([]byte,int(length16)) | |||
| readN,_ := r.Read(msg.ExtraData) | |||
| totalReadN += readN | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalCommitRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractGlobalEndRequestDecoder(in) | |||
| abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest) | |||
| msg := protocal.GlobalCommitRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalCommitResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractGlobalEndResponseDecoder(in) | |||
| abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse) | |||
| msg := protocal.GlobalCommitResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalLockQueryRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := BranchRegisterRequestDecoder(in) | |||
| branchRegisterRequest := req.(protocal.BranchRegisterRequest) | |||
| msg := protocal.GlobalLockQueryRequest{BranchRegisterRequest:branchRegisterRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalLockQueryResponseDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.GlobalLockQueryResponse{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| resultCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.ResultCode = protocal.ResultCode(resultCode) | |||
| if msg.ResultCode == protocal.ResultCodeFailed { | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Msg, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| } | |||
| exceptionCode, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode) | |||
| lockable, readN, _ := r.ReadUint16() | |||
| totalReadN += readN | |||
| if lockable == uint16(1) { | |||
| msg.Lockable = true | |||
| } else if lockable == uint16(0) { | |||
| msg.Lockable = false | |||
| } | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalReportRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.GlobalReportRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.Xid, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ExtraData = make([]byte,int(length16)) | |||
| readN, _ := r.Read(msg.ExtraData) | |||
| totalReadN += readN | |||
| } | |||
| globalStatus,_ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.GlobalStatus = meta.GlobalStatus(globalStatus) | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalReportResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractGlobalEndResponseDecoder(in) | |||
| abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse) | |||
| msg := protocal.GlobalReportResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalRollbackRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractGlobalEndRequestDecoder(in) | |||
| abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest) | |||
| msg := protocal.GlobalRollbackRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalRollbackResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractGlobalEndResponseDecoder(in) | |||
| abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse) | |||
| msg := protocal.GlobalRollbackResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalStatusRequestDecoder(in []byte) (interface{},int) { | |||
| req,totalReadN := AbstractGlobalEndRequestDecoder(in) | |||
| abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest) | |||
| msg := protocal.GlobalStatusRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest} | |||
| return msg,totalReadN | |||
| } | |||
| func GlobalStatusResponseDecoder(in []byte) (interface{},int) { | |||
| resp,totalReadN := AbstractGlobalEndResponseDecoder(in) | |||
| abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse) | |||
| msg := protocal.GlobalStatusResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse} | |||
| return msg,totalReadN | |||
| } | |||
| func UndoLogDeleteRequestDecoder(in []byte) (interface{},int) { | |||
| var ( | |||
| length16 uint16 = 0 | |||
| readN = 0 | |||
| totalReadN = 0 | |||
| ) | |||
| msg := protocal.UndoLogDeleteRequest{} | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(in)} | |||
| branchType, _ := r.ReadByte() | |||
| totalReadN += 1 | |||
| msg.BranchType = meta.BranchType(branchType) | |||
| length16, readN, _ = r.ReadUint16() | |||
| totalReadN += readN | |||
| if length16 > 0 { | |||
| msg.ResourceId, readN, _ = r.ReadString(int(length16)) | |||
| totalReadN += readN | |||
| } | |||
| msg.SaveDays, readN, _ = r.ReadInt16() | |||
| totalReadN += readN | |||
| return msg,totalReadN | |||
| } | |||
| @@ -0,0 +1,560 @@ | |||
| package codec | |||
| import ( | |||
| "bytes" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| func AbstractResultMessageEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| message := in.(protocal.AbstractResultMessage) | |||
| w.WriteByte(byte(message.ResultCode)) | |||
| if message.ResultCode == protocal.ResultCodeFailed { | |||
| var msg string | |||
| if message.Msg != "" { | |||
| if len(message.Msg) > 128 { | |||
| msg = message.Msg[:128] | |||
| } else { | |||
| msg = message.Msg | |||
| } | |||
| // 暂时不考虑 message.Msg 包含中文的情况,这样字符串的长度就是 byte 数组的长度 | |||
| w.WriteInt16(int16(len(msg))) | |||
| w.WriteString(msg) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func MergedWarpMessageEncoder(in interface{}) []byte { | |||
| var ( | |||
| b bytes.Buffer | |||
| result = make([]byte,0) | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.MergedWarpMessage) | |||
| w.WriteInt16(int16(len(req.Msgs))) | |||
| for _,msg := range req.Msgs { | |||
| encoder := getMessageEncoder(msg.GetTypeCode()) | |||
| if encoder != nil { | |||
| data := encoder(msg) | |||
| w.WriteInt16(msg.GetTypeCode()) | |||
| w.Write(data) | |||
| } | |||
| } | |||
| size := uint32(b.Len()) | |||
| result = append(result,[]byte{ byte(size>>24),byte(size>>16),byte(size>>8),byte(size) }...) | |||
| result = append(result, b.Bytes()...) | |||
| if len(req.Msgs)>20 { | |||
| logging.Logger.Debugf("msg in one packet: %s ,buffer size: %s", len(req.Msgs),size) | |||
| } | |||
| return result | |||
| } | |||
| func MergeResultMessageEncoder(in interface{}) []byte { | |||
| var ( | |||
| b bytes.Buffer | |||
| result = make([]byte,0) | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.MergeResultMessage) | |||
| w.WriteInt16(int16(len(req.Msgs))) | |||
| for _,msg := range req.Msgs { | |||
| encoder := getMessageEncoder(msg.GetTypeCode()) | |||
| if encoder != nil { | |||
| data := encoder(msg) | |||
| w.WriteInt16(msg.GetTypeCode()) | |||
| w.Write(data) | |||
| } | |||
| } | |||
| size := uint32(b.Len()) | |||
| result = append(result,[]byte{ byte(size>>24),byte(size>>16),byte(size>>8),byte(size) }...) | |||
| result = append(result, b.Bytes()...) | |||
| if len(req.Msgs)>20 { | |||
| logging.Logger.Debugf("msg in one packet: %s ,buffer size: %s", len(req.Msgs),size) | |||
| } | |||
| return result | |||
| } | |||
| func AbstractIdentifyRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req := in.(protocal.AbstractIdentifyRequest) | |||
| if req.Version != "" { | |||
| w.WriteInt16(int16(len(req.Version))) | |||
| w.WriteString(req.Version) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.ApplicationId != "" { | |||
| w.WriteInt16(int16(len(req.ApplicationId))) | |||
| w.WriteString(req.ApplicationId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.TransactionServiceGroup != "" { | |||
| w.WriteInt16(int16(len(req.TransactionServiceGroup))) | |||
| w.WriteString(req.TransactionServiceGroup) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.ExtraData != nil { | |||
| w.WriteUint16(uint16(len(req.ExtraData))) | |||
| w.Write(req.ExtraData) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func AbstractIdentifyResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.AbstractIdentifyResponse) | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| if resp.Identified { | |||
| w.WriteByte(byte(1)) | |||
| } else { | |||
| w.WriteByte(byte(0)) | |||
| } | |||
| if resp.Version != "" { | |||
| w.WriteInt16(int16(len(resp.Version))) | |||
| w.WriteString(resp.Version) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func RegisterRMRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.RegisterRMRequest) | |||
| data := AbstractIdentifyRequestEncoder(req.AbstractIdentifyRequest) | |||
| var ( | |||
| zero32 int32 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| if req.ResourceIds != "" { | |||
| w.WriteInt32(int32(len(req.ResourceIds))) | |||
| w.WriteString(req.ResourceIds) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| result := append(data,b.Bytes()...) | |||
| return result | |||
| } | |||
| func RegisterRMResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.RegisterRMResponse) | |||
| return AbstractIdentifyResponseEncoder(resp.AbstractIdentifyResponse) | |||
| } | |||
| func RegisterTMRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.RegisterTMRequest) | |||
| return AbstractIdentifyRequestEncoder(req.AbstractIdentifyRequest) | |||
| } | |||
| func RegisterTMResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.RegisterTMResponse) | |||
| return AbstractIdentifyResponseEncoder(resp.AbstractIdentifyResponse) | |||
| } | |||
| func AbstractTransactionResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.AbstractTransactionResponse) | |||
| data := AbstractResultMessageEncoder(resp.AbstractResultMessage) | |||
| result := append(data,byte(resp.TransactionExceptionCode)) | |||
| return result | |||
| } | |||
| func AbstractBranchEndRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero32 int32 = 0 | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.AbstractBranchEndRequest) | |||
| if req.Xid != "" { | |||
| w.WriteInt16(int16(len(req.Xid))) | |||
| w.WriteString(req.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| w.WriteInt64(req.BranchId) | |||
| w.WriteByte(byte(req.BranchType)) | |||
| if req.ResourceId != "" { | |||
| w.WriteInt16(int16(len(req.ResourceId))) | |||
| w.WriteString(req.ResourceId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.ApplicationData != nil { | |||
| w.WriteUint32(uint32(len(req.ApplicationData))) | |||
| w.Write(req.ApplicationData) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func AbstractBranchEndResponseEncoder(in interface{}) []byte { | |||
| resp,_ := in.(protocal.AbstractBranchEndResponse) | |||
| data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| if resp.Xid != "" { | |||
| w.WriteInt16(int16(len(resp.Xid))) | |||
| w.WriteString(resp.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| w.WriteInt64(resp.BranchId) | |||
| w.WriteByte(byte(resp.BranchStatus)) | |||
| result := append(data,b.Bytes()...) | |||
| return result | |||
| } | |||
| func AbstractGlobalEndRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.AbstractGlobalEndRequest) | |||
| if req.Xid != "" { | |||
| w.WriteInt16(int16(len(req.Xid))) | |||
| w.WriteString(req.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.ExtraData != nil { | |||
| w.WriteUint16(uint16(len(req.ExtraData))) | |||
| w.Write(req.ExtraData) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func AbstractGlobalEndResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.AbstractGlobalEndResponse) | |||
| data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| result := append(data,byte(resp.GlobalStatus)) | |||
| return result | |||
| } | |||
| func BranchCommitRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.BranchCommitRequest) | |||
| return AbstractBranchEndRequestEncoder(req.AbstractBranchEndRequest) | |||
| } | |||
| func BranchCommitResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.BranchCommitResponse) | |||
| return AbstractBranchEndResponseEncoder(resp.AbstractBranchEndResponse) | |||
| } | |||
| func BranchRegisterRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero32 int32 = 0 | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.BranchRegisterRequest) | |||
| if req.Xid != "" { | |||
| w.WriteInt16(int16(len(req.Xid))) | |||
| w.WriteString(req.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| w.WriteByte(byte(req.BranchType)) | |||
| if req.ResourceId != "" { | |||
| w.WriteInt16(int16(len(req.ResourceId))) | |||
| w.WriteString(req.ResourceId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.LockKey != "" { | |||
| w.WriteInt32(int32(len(req.LockKey))) | |||
| w.WriteString(req.LockKey) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| if req.ApplicationData != nil { | |||
| w.WriteUint32(uint32(len(req.ApplicationData))) | |||
| w.Write(req.ApplicationData) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func BranchRegisterResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.BranchRegisterResponse) | |||
| data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| c := uint64(resp.BranchId) | |||
| branchIdBytes := []byte{ | |||
| byte(c >> 56), | |||
| byte(c >> 48), | |||
| byte(c >> 40), | |||
| byte(c >> 32), | |||
| byte(c >> 24), | |||
| byte(c >> 16), | |||
| byte(c >> 8), | |||
| byte(c), | |||
| } | |||
| result := append(data,branchIdBytes...) | |||
| return result | |||
| } | |||
| func BranchReportRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero32 int32 = 0 | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.BranchReportRequest) | |||
| if req.Xid != "" { | |||
| w.WriteInt16(int16(len(req.Xid))) | |||
| w.WriteString(req.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| w.WriteInt64(req.BranchId) | |||
| w.WriteByte(byte(req.Status)) | |||
| if req.ResourceId != "" { | |||
| w.WriteInt16(int16(len(req.ResourceId))) | |||
| w.WriteString(req.ResourceId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if req.ApplicationData != nil { | |||
| w.WriteUint32(uint32(len(req.ApplicationData))) | |||
| w.Write(req.ApplicationData) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| w.WriteByte(byte(req.BranchType)) | |||
| return b.Bytes() | |||
| } | |||
| func BranchReportResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.BranchReportResponse) | |||
| return AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| } | |||
| func BranchRollbackRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.BranchRollbackRequest) | |||
| return AbstractBranchEndRequestEncoder(req.AbstractBranchEndRequest) | |||
| } | |||
| func BranchRollbackResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.BranchRollbackResponse) | |||
| return AbstractBranchEndResponseEncoder(resp.AbstractBranchEndResponse) | |||
| } | |||
| func GlobalBeginRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.GlobalBeginRequest) | |||
| w.WriteInt32(req.Timeout) | |||
| if req.TransactionName != "" { | |||
| w.WriteInt16(int16(len(req.TransactionName))) | |||
| w.WriteString(req.TransactionName) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| return b.Bytes() | |||
| } | |||
| func GlobalBeginResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.GlobalBeginResponse) | |||
| data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| if resp.Xid != "" { | |||
| w.WriteInt16(int16(len(resp.Xid))) | |||
| w.WriteString(resp.Xid) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if resp.ExtraData != nil { | |||
| w.WriteUint16(uint16(len(resp.ExtraData))) | |||
| w.Write(resp.ExtraData) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| result := append(data,b.Bytes()...) | |||
| return result | |||
| } | |||
| func GlobalCommitRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.GlobalCommitRequest) | |||
| return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest) | |||
| } | |||
| func GlobalCommitResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.GlobalCommitResponse) | |||
| return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse) | |||
| } | |||
| func GlobalLockQueryRequestEncoder(in interface{}) []byte { | |||
| return BranchRegisterRequestEncoder(in) | |||
| } | |||
| func GlobalLockQueryResponseEncoder(in interface{}) []byte { | |||
| resp,_ := in.(protocal.GlobalLockQueryResponse) | |||
| data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse) | |||
| var result []byte | |||
| if resp.Lockable { | |||
| result = append(data,byte(0),byte(1)) | |||
| } else { | |||
| result = append(data,byte(0),byte(0)) | |||
| } | |||
| return result | |||
| } | |||
| func GlobalReportRequestEncoder(in interface{}) []byte { | |||
| req,_ := in.(protocal.GlobalReportRequest) | |||
| data := AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest) | |||
| result := append(data,byte(req.GlobalStatus)) | |||
| return result | |||
| } | |||
| func GlobalReportResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.GlobalReportResponse) | |||
| return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse) | |||
| } | |||
| func GlobalRollbackRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.GlobalRollbackRequest) | |||
| return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest) | |||
| } | |||
| func GlobalRollbackResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.GlobalRollbackResponse) | |||
| return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse) | |||
| } | |||
| func GlobalStatusRequestEncoder(in interface{}) []byte { | |||
| req := in.(protocal.GlobalStatusRequest) | |||
| return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest) | |||
| } | |||
| func GlobalStatusResponseEncoder(in interface{}) []byte { | |||
| resp := in.(protocal.GlobalStatusResponse) | |||
| return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse) | |||
| } | |||
| func UndoLogDeleteRequestEncoder(in interface{}) []byte { | |||
| var ( | |||
| zero16 int16 = 0 | |||
| b bytes.Buffer | |||
| ) | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| req,_ := in.(protocal.UndoLogDeleteRequest) | |||
| w.WriteByte(byte(req.BranchType)) | |||
| if req.ResourceId != "" { | |||
| w.WriteInt16(int16(len(req.ResourceId))) | |||
| w.WriteString(req.ResourceId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| w.WriteInt16(req.SaveDays) | |||
| return b.Bytes() | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| package protocal | |||
| var MAGIC_CODE_BYTES = [2]byte{ 0xda, 0xda } | |||
| const ( | |||
| VERSION = 1 | |||
| MAX_FRAME_LENGTH = 8 * 1024 * 1024 | |||
| V1_HEAD_LENGTH = 16 | |||
| MSGTYPE_RESQUEST = 0 | |||
| MSGTYPE_RESPONSE = 1 | |||
| MSGTYPE_RESQUEST_ONEWAY = 2 | |||
| MSGTYPE_HEARTBEAT_REQUEST = 3 | |||
| MSGTYPE_HEARTBEAT_RESPONSE = 4 | |||
| ) | |||
| @@ -0,0 +1,16 @@ | |||
| package protocal | |||
| type HeartBeatMessage struct { | |||
| Ping bool | |||
| } | |||
| var HeartBeatMessagePing = HeartBeatMessage{true} | |||
| var HeartBeatMessagePong = HeartBeatMessage{false} | |||
| func (msg HeartBeatMessage) ToString () string { | |||
| if msg.Ping { | |||
| return "services ping" | |||
| } else { | |||
| return "services pong" | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| package protocal | |||
| type AbstractResultMessage struct { | |||
| ResultCode ResultCode | |||
| Msg string | |||
| } | |||
| type AbstractIdentifyRequest struct { | |||
| Version string | |||
| ApplicationId string | |||
| TransactionServiceGroup string | |||
| ExtraData []byte | |||
| } | |||
| type AbstractIdentifyResponse struct { | |||
| AbstractResultMessage | |||
| Version string | |||
| ExtraData []byte | |||
| Identified bool | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package protocal | |||
| type MergedWarpMessage struct { | |||
| Msgs []MessageTypeAware | |||
| MsgIds []int32 | |||
| } | |||
| func (req MergedWarpMessage) GetTypeCode() int16 { | |||
| return TypeSeataMerge | |||
| } | |||
| type MergeResultMessage struct { | |||
| Msgs []MessageTypeAware | |||
| } | |||
| func (resp MergeResultMessage) GetTypeCode() int16 { | |||
| return TypeSeataMergeResult | |||
| } | |||
| @@ -0,0 +1,117 @@ | |||
| package protocal | |||
| type MessageType int16 | |||
| const ( | |||
| /** | |||
| * The constant TYPE_GLOBAL_BEGIN. | |||
| */ | |||
| TypeGlobalBegin = 1 | |||
| /** | |||
| * The constant TYPE_GLOBAL_BEGIN_RESULT. | |||
| */ | |||
| TypeGlobalBeginResult = 2 | |||
| /** | |||
| * The constant TYPE_GLOBAL_COMMIT. | |||
| */ | |||
| TypeGlobalCommit = 7 | |||
| /** | |||
| * The constant TYPE_GLOBAL_COMMIT_RESULT. | |||
| */ | |||
| TypeGlobalCommitResult = 8 | |||
| /** | |||
| * The constant TYPE_GLOBAL_ROLLBACK. | |||
| */ | |||
| TypeGlobalRollback = 9 | |||
| /** | |||
| * The constant TYPE_GLOBAL_ROLLBACK_RESULT. | |||
| */ | |||
| TypeGlobalRollbackResult = 10 | |||
| /** | |||
| * The constant TYPE_GLOBAL_STATUS. | |||
| */ | |||
| TypeGlobalStatus = 15 | |||
| /** | |||
| * The constant TYPE_GLOBAL_STATUS_RESULT. | |||
| */ | |||
| TypeGlobalStatusResult = 16 | |||
| /** | |||
| * The constant TYPE_GLOBAL_REPORT. | |||
| */ | |||
| TypeGlobalReport = 17 | |||
| /** | |||
| * The constant TYPE_GLOBAL_REPORT_RESULT. | |||
| */ | |||
| TypeGlobalReportResult = 18 | |||
| /** | |||
| * The constant TYPE_GLOBAL_LOCK_QUERY. | |||
| */ | |||
| TypeGlobalLockQuery = 21 | |||
| /** | |||
| * The constant TYPE_GLOBAL_LOCK_QUERY_RESULT. | |||
| */ | |||
| TypeGlobalLockQueryResult = 22 | |||
| /** | |||
| * The constant TYPE_BRANCH_COMMIT. | |||
| */ | |||
| TypeBranchCommit = 3 | |||
| /** | |||
| * The constant TYPE_BRANCH_COMMIT_RESULT. | |||
| */ | |||
| TypeBranchCommitResult = 4 | |||
| /** | |||
| * The constant TYPE_BRANCH_ROLLBACK. | |||
| */ | |||
| TypeBranchRollback = 5 | |||
| /** | |||
| * The constant TYPE_BRANCH_ROLLBACK_RESULT. | |||
| */ | |||
| TypeBranchRollbackResult = 6 | |||
| /** | |||
| * The constant TYPE_BRANCH_REGISTER. | |||
| */ | |||
| TypeBranchRegister = 11 | |||
| /** | |||
| * The constant TYPE_BRANCH_REGISTER_RESULT. | |||
| */ | |||
| TypeBranchRegisterResult = 12 | |||
| /** | |||
| * The constant TYPE_BRANCH_STATUS_REPORT. | |||
| */ | |||
| TypeBranchStatusReport = 13 | |||
| /** | |||
| * The constant TYPE_BRANCH_STATUS_REPORT_RESULT. | |||
| */ | |||
| TypeBranchStatusReportResult = 14 | |||
| /** | |||
| * The constant TYPE_SEATA_MERGE. | |||
| */ | |||
| TypeSeataMerge = 59 | |||
| /** | |||
| * The constant TYPE_SEATA_MERGE_RESULT. | |||
| */ | |||
| TypeSeataMergeResult = 60 | |||
| /** | |||
| * The constant TYPE_REG_CLT. | |||
| */ | |||
| TypeRegClt = 101 | |||
| /** | |||
| * The constant TYPE_REG_CLT_RESULT. | |||
| */ | |||
| TypeRegCltResult = 102 | |||
| /** | |||
| * The constant TYPE_REG_RM. | |||
| */ | |||
| TypeRegRm = 103 | |||
| /** | |||
| * The constant TYPE_REG_RM_RESULT. | |||
| */ | |||
| TypeRegRmResult = 104 | |||
| /** | |||
| * The constant TYPE_RM_DELETE_UNDOLOG. | |||
| */ | |||
| TypeRmDeleteUndolog = 111 | |||
| ) | |||
| @@ -0,0 +1,5 @@ | |||
| package protocal | |||
| type MessageTypeAware interface { | |||
| GetTypeCode() int16 | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package protocal | |||
| type ResultCode byte | |||
| const ( | |||
| /** | |||
| * ResultCodeFailed result code. | |||
| */ | |||
| // ResultCodeFailed | |||
| ResultCodeFailed ResultCode = iota | |||
| /** | |||
| * Success result code. | |||
| */ | |||
| // Success | |||
| ResultCodeSuccess | |||
| ) | |||
| @@ -0,0 +1,18 @@ | |||
| package protocal | |||
| type RegisterRMRequest struct { | |||
| AbstractIdentifyRequest | |||
| ResourceIds string | |||
| } | |||
| func (req RegisterRMRequest) GetTypeCode() int16 { | |||
| return TypeRegRm | |||
| } | |||
| type RegisterRMResponse struct { | |||
| AbstractIdentifyResponse | |||
| } | |||
| func (resp RegisterRMResponse) GetTypeCode() int16 { | |||
| return TypeRegRmResult | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| package protocal | |||
| type RpcMessage struct { | |||
| Id int32 | |||
| MessageType byte | |||
| Codec byte | |||
| Compressor byte | |||
| HeadMap map[string]string | |||
| Body interface{} | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package protocal | |||
| type RegisterTMRequest struct { | |||
| AbstractIdentifyRequest | |||
| } | |||
| func (req RegisterTMRequest) GetTypeCode() int16 { | |||
| return TypeRegClt | |||
| } | |||
| type RegisterTMResponse struct { | |||
| AbstractIdentifyResponse | |||
| } | |||
| func (resp RegisterTMResponse) GetTypeCode() int16 { | |||
| return TypeRegCltResult | |||
| } | |||
| @@ -0,0 +1,227 @@ | |||
| package protocal | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| ) | |||
| type AbstractTransactionResponse struct { | |||
| AbstractResultMessage | |||
| TransactionExceptionCode meta.TransactionExceptionCode | |||
| } | |||
| type AbstractBranchEndRequest struct { | |||
| Xid string | |||
| BranchId int64 | |||
| BranchType meta.BranchType | |||
| ResourceId string | |||
| ApplicationData []byte | |||
| } | |||
| type AbstractBranchEndResponse struct { | |||
| AbstractTransactionResponse | |||
| Xid string | |||
| BranchId int64 | |||
| BranchStatus meta.BranchStatus | |||
| } | |||
| type AbstractGlobalEndRequest struct { | |||
| Xid string | |||
| ExtraData []byte | |||
| } | |||
| type AbstractGlobalEndResponse struct { | |||
| AbstractTransactionResponse | |||
| GlobalStatus meta.GlobalStatus | |||
| } | |||
| type BranchRegisterRequest struct { | |||
| Xid string | |||
| BranchType meta.BranchType | |||
| ResourceId string | |||
| LockKey string | |||
| ApplicationData []byte | |||
| } | |||
| func (req BranchRegisterRequest) GetTypeCode() int16 { | |||
| return TypeBranchRegister | |||
| } | |||
| type BranchRegisterResponse struct { | |||
| AbstractTransactionResponse | |||
| BranchId int64 | |||
| } | |||
| func (resp BranchRegisterResponse) GetTypeCode() int16 { | |||
| return TypeBranchRegisterResult | |||
| } | |||
| type BranchReportRequest struct { | |||
| Xid string | |||
| BranchId int64 | |||
| ResourceId string | |||
| Status meta.BranchStatus | |||
| ApplicationData []byte | |||
| BranchType meta.BranchType | |||
| } | |||
| func (req BranchReportRequest) GetTypeCode() int16 { | |||
| return TypeBranchStatusReport | |||
| } | |||
| type BranchReportResponse struct { | |||
| AbstractTransactionResponse | |||
| } | |||
| func (resp BranchReportResponse) GetTypeCode() int16 { | |||
| return TypeBranchStatusReportResult | |||
| } | |||
| type BranchCommitRequest struct { | |||
| AbstractBranchEndRequest | |||
| } | |||
| func (req BranchCommitRequest) GetTypeCode() int16 { | |||
| return TypeBranchCommit | |||
| } | |||
| type BranchCommitResponse struct { | |||
| AbstractBranchEndResponse | |||
| } | |||
| func (resp BranchCommitResponse) GetTypeCode() int16 { | |||
| return TypeBranchCommitResult | |||
| } | |||
| type BranchRollbackRequest struct { | |||
| AbstractBranchEndRequest | |||
| } | |||
| func (req BranchRollbackRequest) GetTypeCode() int16 { | |||
| return TypeBranchRollback | |||
| } | |||
| type BranchRollbackResponse struct { | |||
| AbstractBranchEndResponse | |||
| } | |||
| func (resp BranchRollbackResponse) GetTypeCode() int16 { | |||
| return TypeGlobalRollbackResult | |||
| } | |||
| type GlobalBeginRequest struct { | |||
| Timeout int32 | |||
| TransactionName string | |||
| } | |||
| func (req GlobalBeginRequest) GetTypeCode() int16 { | |||
| return TypeGlobalBegin | |||
| } | |||
| type GlobalBeginResponse struct { | |||
| AbstractTransactionResponse | |||
| Xid string | |||
| ExtraData []byte | |||
| } | |||
| func (resp GlobalBeginResponse) GetTypeCode() int16 { | |||
| return TypeGlobalBeginResult | |||
| } | |||
| type GlobalStatusRequest struct { | |||
| AbstractGlobalEndRequest | |||
| } | |||
| func (req GlobalStatusRequest) GetTypeCode() int16 { | |||
| return TypeGlobalStatus | |||
| } | |||
| type GlobalStatusResponse struct { | |||
| AbstractGlobalEndResponse | |||
| } | |||
| func (resp GlobalStatusResponse) GetTypeCode() int16 { | |||
| return TypeGlobalStatusResult | |||
| } | |||
| type GlobalLockQueryRequest struct { | |||
| BranchRegisterRequest | |||
| } | |||
| func (req GlobalLockQueryRequest) GetTypeCode() int16 { | |||
| return TypeGlobalLockQuery | |||
| } | |||
| type GlobalLockQueryResponse struct { | |||
| AbstractTransactionResponse | |||
| Lockable bool | |||
| } | |||
| func (resp GlobalLockQueryResponse) GetTypeCode() int16 { | |||
| return TypeGlobalLockQueryResult | |||
| } | |||
| type GlobalReportRequest struct { | |||
| AbstractGlobalEndRequest | |||
| GlobalStatus meta.GlobalStatus | |||
| } | |||
| func (req GlobalReportRequest) GetTypeCode() int16 { | |||
| return TypeGlobalStatus | |||
| } | |||
| type GlobalReportResponse struct { | |||
| AbstractGlobalEndResponse | |||
| } | |||
| func (resp GlobalReportResponse) GetTypeCode() int16 { | |||
| return TypeGlobalStatusResult | |||
| } | |||
| type GlobalCommitRequest struct { | |||
| AbstractGlobalEndRequest | |||
| } | |||
| func (req GlobalCommitRequest) GetTypeCode() int16 { | |||
| return TypeGlobalCommit | |||
| } | |||
| type GlobalCommitResponse struct { | |||
| AbstractGlobalEndResponse | |||
| } | |||
| func (resp GlobalCommitResponse) GetTypeCode() int16 { | |||
| return TypeGlobalCommitResult | |||
| } | |||
| type GlobalRollbackRequest struct { | |||
| AbstractGlobalEndRequest | |||
| } | |||
| func (req GlobalRollbackRequest) GetTypeCode() int16 { | |||
| return TypeGlobalRollback | |||
| } | |||
| type GlobalRollbackResponse struct { | |||
| AbstractGlobalEndResponse | |||
| } | |||
| func (resp GlobalRollbackResponse) GetTypeCode() int16 { | |||
| return TypeGlobalRollbackResult | |||
| } | |||
| type UndoLogDeleteRequest struct { | |||
| ResourceId string | |||
| SaveDays int16 | |||
| BranchType meta.BranchType | |||
| } | |||
| func (req UndoLogDeleteRequest) GetTypeCode() int16 { | |||
| return TypeRmDeleteUndolog | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package rm | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/model" | |||
| ) | |||
| type IResourceManagerInbound interface { | |||
| /** | |||
| * Commit a branch transaction. | |||
| * | |||
| * @param branchType the branch type | |||
| * @param xid Transaction id. | |||
| * @param branchId Branch id. | |||
| * @param resourceId Resource id. | |||
| * @param applicationData Application data bind with this branch. | |||
| * @return Status of the branch after committing. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| BranchCommit(branchType meta.BranchType, xid string, branchId int64, resourceId string, applicationData []byte) (meta.BranchStatus, error) | |||
| /** | |||
| * Rollback a branch transaction. | |||
| * | |||
| * @param branchType the branch type | |||
| * @param xid Transaction id. | |||
| * @param branchId Branch id. | |||
| * @param resourceId Resource id. | |||
| * @param applicationData Application data bind with this branch. | |||
| * @return Status of the branch after rollbacking. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| BranchRollback(branchType meta.BranchType, xid string, branchId int64, resourceId string, applicationData []byte) (meta.BranchStatus, error) | |||
| } | |||
| type IResourceManagerOutbound interface { | |||
| /** | |||
| * Branch register long. | |||
| * | |||
| * @param branchType the branch type | |||
| * @param resourceId the resource id | |||
| * @param clientId the client id | |||
| * @param xid the xid | |||
| * @param applicationData the context | |||
| * @param lockKeys the lock keys | |||
| * @return the long | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| BranchRegister(branchType meta.BranchType, resourceId string, clientId string, xid string, applicationData []byte, lockKeys string) (int64, error) | |||
| /** | |||
| * Branch report. | |||
| * | |||
| * @param branchType the branch type | |||
| * @param xid the xid | |||
| * @param branchId the branch id | |||
| * @param status the status | |||
| * @param applicationData the application data | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| BranchReport(branchType meta.BranchType, xid string, branchId int64, status meta.BranchStatus, applicationData []byte) error | |||
| /** | |||
| * Lock query boolean. | |||
| * | |||
| * @param branchType the branch type | |||
| * @param resourceId the resource id | |||
| * @param xid the xid | |||
| * @param lockKeys the lock keys | |||
| * @return the boolean | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| LockQuery(branchType meta.BranchType, resourceId string, xid string, lockKeys string) (bool, error) | |||
| } | |||
| type IResourceManager interface { | |||
| IResourceManagerInbound | |||
| IResourceManagerOutbound | |||
| /** | |||
| * Register a Resource to be managed by Resource Manager. | |||
| * | |||
| * @param resource The resource to be managed. | |||
| */ | |||
| registerResource(resource model.IResource) | |||
| /** | |||
| * Unregister a Resource from the Resource Manager. | |||
| * | |||
| * @param resource The resource to be removed. | |||
| */ | |||
| unregisterResource(resource model.IResource) | |||
| /** | |||
| * Get all resources managed by this manager. | |||
| * | |||
| * @return resourceId -> Resource Map | |||
| */ | |||
| getManagedResources() map[string]model.IResource | |||
| /** | |||
| * Get the BranchType. | |||
| * | |||
| * @return The BranchType of ResourceManager. | |||
| */ | |||
| getBranchType() meta.BranchType | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| package main | |||
| import ( | |||
| "os" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/tc/server" | |||
| ) | |||
| const ( | |||
| APP_CONF_FILE = "APP_CONF_FILE" | |||
| ) | |||
| func main() { | |||
| confFile := os.Getenv(APP_CONF_FILE) | |||
| config.InitConf(confFile) | |||
| server.SetServerGrpool() | |||
| srv := server.NewServer() | |||
| conf := config.GetServerConfig() | |||
| srv.Start(conf.Host+":"+conf.Port) | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| host: "127.0.0.1" | |||
| port: "8091" | |||
| timeout_retry_period: "1s" | |||
| rollbacking_retry_period: "1s" | |||
| committing_retry_period: "1s" | |||
| aync_committing_retry_period: "1s" | |||
| log_delete_period: "24h" | |||
| getty_config: | |||
| session_timeout : "20s" | |||
| session_number : 700 | |||
| getty_session_param: | |||
| compress_encoding : false | |||
| tcp_no_delay : true | |||
| tcp_keep_alive : true | |||
| keep_alive_period : "120s" | |||
| tcp_r_buf_size : 262144 | |||
| tcp_w_buf_size : 524288 | |||
| pkg_rq_size : 1024 | |||
| pkg_wq_size : 512 | |||
| tcp_read_timeout : "1s" | |||
| tcp_write_timeout : "5s" | |||
| wait_timeout : "1s" | |||
| max_msg_len : 4096 | |||
| session_name : "seata-server" | |||
| store_config: | |||
| max_global_session_size: 512 | |||
| max_branch_session_size: 16384 | |||
| @@ -0,0 +1,38 @@ | |||
| package config | |||
| import "time" | |||
| type GettySessionParam struct { | |||
| CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` | |||
| TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` | |||
| TcpKeepAlive bool `default:"true" yaml:"tcp_keep_alive" json:"tcp_keep_alive,omitempty"` | |||
| KeepAlivePrd string `default:"180s" yaml:"keep_alive_period" json:"keep_alive_period,omitempty"` | |||
| KeepAlivePeriod time.Duration | |||
| TcpRBufSize int `default:"262144" yaml:"tcp_r_buf_size" json:"tcp_r_buf_size,omitempty"` | |||
| TcpWBufSize int `default:"65536" yaml:"tcp_w_buf_size" json:"tcp_w_buf_size,omitempty"` | |||
| PkgWQSize int `default:"1024" yaml:"pkg_wq_size" json:"pkg_wq_size,omitempty"` | |||
| TcpReadTmt string `default:"1s" yaml:"tcp_read_timeout" json:"tcp_read_timeout,omitempty"` | |||
| TcpReadTimeout time.Duration | |||
| TcpWriteTmt string `default:"5s" yaml:"tcp_write_timeout" json:"tcp_write_timeout,omitempty"` | |||
| TcpWriteTimeout time.Duration | |||
| WaitTmt string `default:"7s" yaml:"wait_timeout" json:"wait_timeout,omitempty"` | |||
| WaitTimeout time.Duration | |||
| MaxMsgLen int `default:"1024" yaml:"max_msg_len" json:"max_msg_len,omitempty"` | |||
| SessionName string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"` | |||
| } | |||
| // Config holds supported types by the multiconfig package | |||
| type GettyConfig struct { | |||
| // session | |||
| SessionTmt string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` | |||
| SessionTimeout time.Duration | |||
| SessionNumber int `default:"1000" yaml:"session_number" json:"session_number,omitempty"` | |||
| // grpool | |||
| GrPoolSize int `default:"0" yaml:"gr_pool_size" json:"gr_pool_size,omitempty"` | |||
| QueueLen int `default:"0" yaml:"queue_len" json:"queue_len,omitempty"` | |||
| QueueNumber int `default:"0" yaml:"queue_number" json:"queue_number,omitempty"` | |||
| // session tcp parameters | |||
| GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` | |||
| } | |||
| @@ -0,0 +1,104 @@ | |||
| package config | |||
| import ( | |||
| "fmt" | |||
| "github.com/pkg/errors" | |||
| "gopkg.in/yaml.v2" | |||
| "io/ioutil" | |||
| "path" | |||
| "time" | |||
| ) | |||
| var ( | |||
| conf ServerConfig | |||
| ) | |||
| func GetServerConfig() ServerConfig { | |||
| return conf | |||
| } | |||
| type ServerConfig struct { | |||
| Host string `default:"127.0.0.1" yaml:"host" json:"host,omitempty"` | |||
| Port string `default:"8091" yaml:"port" json:"port,omitempty"` | |||
| MaxRollbackRetryTimeout int64 `default:"-1" yaml:"max_rollback_retry_timeout" json:"max_rollback_retry_timeout,omitempty"` | |||
| RollbackRetryTimeoutUnlockEnable bool `default:"false" yaml:"rollback_retry_timeout_unlock_enable" json:"rollback_retry_timeout_unlock_enable,omitempty"` | |||
| MaxCommitRetryTimeout int64 `default:"-1" yaml:"max_commit_retry_timeout" json:"max_commit_retry_timeout,omitempty"` | |||
| TimeoutRetryPrd string `default:"1s" yaml:"timeout_retry_period" json:"timeout_retry_period,omitempty"` | |||
| TimeoutRetryPeriod time.Duration | |||
| RollbackingRetryPrd string `default:"1s" yaml:"rollbacking_retry_period" json:"rollbacking_retry_period,omitempty"` | |||
| RollbackingRetryPeriod time.Duration | |||
| CommittingRetryPrd string `default:"1s" yaml:"committing_retry_period" json:"committing_retry_period,omitempty"` | |||
| CommittingRetryPeriod time.Duration | |||
| AsynCommittingRetryPrd string `default:"1s" yaml:"aync_committing_retry_period" json:"aync_committing_retry_period,omitempty"` | |||
| AsynCommittingRetryPeriod time.Duration | |||
| LogDeletePrd string `default:"24h" yaml:"log_delete_period" json:"log_delete_period,omitempty"` | |||
| LogDeletePeriod time.Duration | |||
| GettyConfig GettyConfig `required:"true" yaml:"getty_config" json:"getty_config,omitempty"` | |||
| UndoConfig UndoConfig `required:"true" yaml:"undo_config" json:"undo_config,omitempty"` | |||
| StoreConfig StoreConfig `required:"true" yaml:"store_config" json:"store_config,omitempty"` | |||
| } | |||
| func InitConf(confFile string) error { | |||
| var err error | |||
| if confFile == "" { | |||
| return errors.WithMessagef(err,fmt.Sprintf("application configure file name is nil")) | |||
| } | |||
| if path.Ext(confFile) != ".yml" { | |||
| return errors.WithMessagef(err,fmt.Sprintf("application configure file name{%v} suffix must be .yml", confFile)) | |||
| } | |||
| conf = ServerConfig{} | |||
| confFileStream, err := ioutil.ReadFile(confFile) | |||
| if err != nil { | |||
| return errors.WithMessagef(err,fmt.Sprintf("ioutil.ReadFile(file:%s) = error:%s", confFile, err)) | |||
| } | |||
| err = yaml.Unmarshal(confFileStream, &conf) | |||
| if err != nil { | |||
| return errors.WithMessagef(err,fmt.Sprintf("yaml.Unmarshal() = error:%s", err)) | |||
| } | |||
| if conf.TimeoutRetryPeriod, err = time.ParseDuration(conf.TimeoutRetryPrd); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(TimeoutRetryPrd{%#v})", conf.TimeoutRetryPrd) | |||
| } | |||
| conf.RollbackingRetryPeriod, err = time.ParseDuration(conf.RollbackingRetryPrd) | |||
| if err != nil { | |||
| return errors.WithMessagef(err,"time.ParseDuration(RollbackingRetryPrd{%#v})", conf.RollbackingRetryPrd) | |||
| } | |||
| if conf.CommittingRetryPeriod, err = time.ParseDuration(conf.CommittingRetryPrd); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(CommittingRetryPrd{%#v})", conf.CommittingRetryPrd) | |||
| } | |||
| if conf.AsynCommittingRetryPeriod, err = time.ParseDuration(conf.AsynCommittingRetryPrd); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(AsynCommittingRetryPrd{%#v})", conf.AsynCommittingRetryPrd) | |||
| } | |||
| if conf.LogDeletePeriod, err = time.ParseDuration(conf.LogDeletePrd); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(LogDeletePrd{%#v})", conf.LogDeletePrd) | |||
| } | |||
| conf.GettyConfig.SessionTimeout, err = time.ParseDuration(conf.GettyConfig.SessionTmt) | |||
| if err != nil { | |||
| return errors.WithMessagef(err,fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.GettyConfig.SessionTmt, err)) | |||
| } | |||
| if conf.GettyConfig.GettySessionParam.KeepAlivePeriod, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.KeepAlivePrd); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(KeepAlivePeriod{%#v})", conf.GettyConfig.GettySessionParam.KeepAlivePrd) | |||
| } | |||
| if conf.GettyConfig.GettySessionParam.TcpReadTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.TcpReadTmt); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(TcpReadTimeout{%#v})", conf.GettyConfig.GettySessionParam.TcpReadTmt) | |||
| } | |||
| if conf.GettyConfig.GettySessionParam.TcpWriteTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.TcpWriteTmt); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(TcpWriteTimeout{%#v})", conf.GettyConfig.GettySessionParam.TcpWriteTmt) | |||
| } | |||
| if conf.GettyConfig.GettySessionParam.WaitTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.WaitTmt); err != nil { | |||
| return errors.WithMessagef(err, "time.ParseDuration(WaitTimeout{%#v})", conf.GettyConfig.GettySessionParam.WaitTmt) | |||
| } | |||
| storeConfig = conf.StoreConfig | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,58 @@ | |||
| package config | |||
| const ( | |||
| DefaultFileDir = "root.data" | |||
| DefaultMaxBranchSessionSize = 1024 * 16 | |||
| DefaultMaxGlobalSessionSize = 512 | |||
| DefaultWriteBufferSize = 1024 * 16 | |||
| DefualtServiceSessionReloadReadSize = 100 | |||
| ) | |||
| type FlushDiskMode int | |||
| const ( | |||
| /** | |||
| * sync flush disk | |||
| */ | |||
| FlushdiskModeSyncModel FlushDiskMode = iota | |||
| /** | |||
| * async flush disk | |||
| */ | |||
| FlushdiskModeAsyncModel | |||
| ) | |||
| type StoreConfig struct { | |||
| MaxBranchSessionSize int `default:"16384" yaml:"max_branch_session_size" json:"max_branch_session_size,omitempty"` | |||
| MaxGlobalSessionSize int `default:"512" yaml:"max_global_session_size" json:"max_global_session_size,omitempty"` | |||
| StoreMode string `default:"file" yaml:"mode" json:"mode,omitempty"` | |||
| FileStoreConfig FileStoreConfig `yaml:"file" json:"file,omitempty"` | |||
| DBStoreConfig DBStoreConfig `yaml:"db" json:"db,omitempty"` | |||
| } | |||
| type FileStoreConfig struct { | |||
| FileDir string `default:"root.data" yaml:"file_dir" json:"file_dir,omitempty"` | |||
| FileWriteBufferCacheSize int `default:"16384" yaml:"file_write_buffer_cache_size" json:"file_write_buffer_cache_size,omitempty"` | |||
| FlushDiskMode FlushDiskMode `default:"1" yaml:"flush_disk_mode" json:"flush_disk_mode,omitempty"` | |||
| SessionReloadReadSize int `default:"100" yaml:"session_reload_read_size" json:"session_reload_read_size,omitempty"` | |||
| } | |||
| type DBStoreConfig struct { | |||
| } | |||
| var storeConfig StoreConfig | |||
| func GetStoreConfig() StoreConfig { | |||
| return storeConfig | |||
| } | |||
| func GetDefaultFileStoreConfig() FileStoreConfig{ | |||
| return FileStoreConfig{ | |||
| FileDir: DefaultFileDir, | |||
| FileWriteBufferCacheSize: DefaultWriteBufferSize, | |||
| FlushDiskMode: 0, | |||
| SessionReloadReadSize: DefualtServiceSessionReloadReadSize, | |||
| } | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| package config | |||
| type UndoConfig struct { | |||
| LogSaveDays int16 `default:"7" yaml:"log_save_days" json:"log_save_days,omitempty"` | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| package event | |||
| type EventManager struct { | |||
| GlobalTransactionEventChannel chan GlobalTransactionEvent | |||
| } | |||
| var EventBus EventManager | |||
| func init() { | |||
| EventBus = EventManager{GlobalTransactionEventChannel:make(chan GlobalTransactionEvent,1000)} | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| package event | |||
| import "github.com/dk-lockdown/seata-golang/meta" | |||
| const ( | |||
| RoleTC = "tc" | |||
| RoleTM = "tm" | |||
| RoleRM = "rm" | |||
| ) | |||
| type GlobalTransactionEvent struct { | |||
| id int64 | |||
| role string | |||
| name string | |||
| beginTime int64 | |||
| endTime int64 | |||
| status meta.GlobalStatus | |||
| } | |||
| func NewGlobalTransactionEvent(id int64,role string,name string,beginTime int64,endTime int64,status meta.GlobalStatus) GlobalTransactionEvent { | |||
| return GlobalTransactionEvent{ | |||
| id, | |||
| role, | |||
| name, | |||
| beginTime, | |||
| endTime, | |||
| status, | |||
| } | |||
| } | |||
| func (event GlobalTransactionEvent) GetId() int64 { return event.id } | |||
| func (event GlobalTransactionEvent) GetRole() string { return event.role } | |||
| func (event GlobalTransactionEvent) GetName() string { return event.name } | |||
| func (event GlobalTransactionEvent) GetBeginTime() int64 { return event.beginTime } | |||
| func (event GlobalTransactionEvent) GetEndTime() int64 { return event.endTime } | |||
| func (event GlobalTransactionEvent) GetStatus() meta.GlobalStatus { return event.status } | |||
| @@ -0,0 +1,66 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/tc/model" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| ) | |||
| type DefaultSessionManager struct { | |||
| AbstractSessionManager | |||
| SessionMap map[string]*session.GlobalSession | |||
| } | |||
| func NewDefaultSessionManager(name string) ISessionManager { | |||
| return &DefaultSessionManager{ | |||
| AbstractSessionManager: AbstractSessionManager { | |||
| TransactionStoreManager: &AbstractTransactionStoreManager{}, | |||
| Name: name, | |||
| }, | |||
| SessionMap: make(map[string]*session.GlobalSession), | |||
| } | |||
| } | |||
| func (sessionManager *DefaultSessionManager) AddGlobalSession(session *session.GlobalSession) error { | |||
| sessionManager.AbstractSessionManager.AddGlobalSession(session) | |||
| sessionManager.SessionMap[session.Xid] = session | |||
| return nil | |||
| } | |||
| func (sessionManager *DefaultSessionManager) FindGlobalSession(xid string) *session.GlobalSession { | |||
| return sessionManager.SessionMap[xid] | |||
| } | |||
| func (sessionManager *DefaultSessionManager) FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession { | |||
| return sessionManager.SessionMap[xid] | |||
| } | |||
| func (sessionManager *DefaultSessionManager) RemoveGlobalSession(session *session.GlobalSession) error{ | |||
| sessionManager.AbstractSessionManager.RemoveGlobalSession(session) | |||
| delete(sessionManager.SessionMap,session.Xid) | |||
| return nil | |||
| } | |||
| func (sessionManager *DefaultSessionManager) AllSessions() []*session.GlobalSession { | |||
| var sessions = make([]*session.GlobalSession,0) | |||
| for _,session := range sessionManager.SessionMap { | |||
| sessions = append(sessions,session) | |||
| } | |||
| return sessions | |||
| } | |||
| func (sessionManager *DefaultSessionManager) FindGlobalSessions(condition model.SessionCondition) []*session.GlobalSession { | |||
| var sessions = make([]*session.GlobalSession,0) | |||
| for _,session := range sessionManager.SessionMap { | |||
| if int64(util.CurrentTimeMillis()) - session.BeginTime > condition.OverTimeAliveMills { | |||
| sessions = append(sessions, session) | |||
| } | |||
| } | |||
| return sessions | |||
| } | |||
| func (sessionManager *DefaultSessionManager) SetTransactionStoreManager(transactionStoreManager ITransactionStoreManager) { | |||
| sessionManager.TransactionStoreManager = transactionStoreManager | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/dk-lockdown/seata-golang/common" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "testing" | |||
| ) | |||
| func TestDefaultSessionManager_AddGlobalSession_RemoveGlobalSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewDefaultSessionManager("default") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestDefaultSessionManager_FindGlobalSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewDefaultSessionManager("default") | |||
| sessionManager.AddGlobalSession(gs) | |||
| expected := sessionManager.FindGlobalSession(gs.Xid) | |||
| assert.NotNil(t,expected) | |||
| assert.Equal(t,gs.TransactionId,expected.TransactionId) | |||
| assert.Equal(t,gs.ApplicationId,expected.ApplicationId) | |||
| assert.Equal(t,gs.TransactionServiceGroup,expected.TransactionServiceGroup) | |||
| assert.Equal(t,gs.TransactionName,expected.TransactionName) | |||
| assert.Equal(t,gs.Status,expected.Status) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func globalSessionsProvider() []*session.GlobalSession { | |||
| common.XID.IpAddress="127.0.0.1" | |||
| common.XID.Port=9876 | |||
| result := make([]*session.GlobalSession,0) | |||
| gs1 := session.NewGlobalSession(). | |||
| SetApplicationId("demo-app"). | |||
| SetTransactionId(util.GeneratorUUID()). | |||
| SetTransactionServiceGroup("my_test_tx_group"). | |||
| SetTransactionName("test"). | |||
| SetTimeout(6000) | |||
| gs1.SetXid(common.XID.GenerateXID(gs1.TransactionId)) | |||
| gs2 := session.NewGlobalSession(). | |||
| SetApplicationId("demo-app"). | |||
| SetTransactionId(util.GeneratorUUID()). | |||
| SetTransactionServiceGroup("my_test_tx_group"). | |||
| SetTransactionName("test"). | |||
| SetTimeout(6000) | |||
| gs2.SetXid(common.XID.GenerateXID(gs2.TransactionId)) | |||
| result = append(result,gs1) | |||
| result = append(result,gs2) | |||
| return result | |||
| } | |||
| func globalSessionProvider() *session.GlobalSession { | |||
| common.XID.IpAddress="127.0.0.1" | |||
| common.XID.Port=9876 | |||
| gs := session.NewGlobalSession(). | |||
| SetApplicationId("demo-app"). | |||
| SetTransactionId(util.GeneratorUUID()). | |||
| SetTransactionServiceGroup("my_test_tx_group"). | |||
| SetTransactionName("test"). | |||
| SetTimeout(6000) | |||
| gs.SetXid(common.XID.GenerateXID(gs.TransactionId)) | |||
| return gs | |||
| } | |||
| func branchSessionProvider(globalSession *session.GlobalSession) *session.BranchSession { | |||
| bs := session.NewBranchSession(). | |||
| SetTransactionId(globalSession.TransactionId). | |||
| SetBranchId(1). | |||
| SetResourceGroupId("my_test_tx_group"). | |||
| SetResourceId("tb_1"). | |||
| SetLockKey("t_1"). | |||
| SetBranchType(meta.BranchTypeAT). | |||
| SetApplicationData([]byte("{\"data\":\"test\"}")) | |||
| return bs | |||
| } | |||
| @@ -0,0 +1,215 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| ) | |||
| type Reloadable interface { | |||
| /** | |||
| * Reload states. | |||
| */ | |||
| Reload() | |||
| } | |||
| type FileBasedSessionManager struct { | |||
| conf config.FileStoreConfig | |||
| DefaultSessionManager | |||
| } | |||
| func NewFileBasedSessionManager(conf config.FileStoreConfig) ISessionManager { | |||
| transactionStoreManager := &FileTransactionStoreManager{} | |||
| transactionStoreManager.InitFile(conf.FileDir) | |||
| sessionManager := DefaultSessionManager{ | |||
| AbstractSessionManager: AbstractSessionManager{ | |||
| TransactionStoreManager: transactionStoreManager, | |||
| Name: conf.FileDir, | |||
| }, | |||
| SessionMap: make(map[string]*session.GlobalSession), | |||
| } | |||
| transactionStoreManager.SessionManager = &sessionManager | |||
| return &FileBasedSessionManager{ | |||
| conf: conf, | |||
| DefaultSessionManager: sessionManager, | |||
| } | |||
| } | |||
| func (sessionManager *FileBasedSessionManager) Reload() { | |||
| sessionManager.restoreSessions() | |||
| sessionManager.washSessions() | |||
| } | |||
| func (sessionManager *FileBasedSessionManager) restoreSessions() { | |||
| unhandledBranchBuffer := make(map[int64]*session.BranchSession) | |||
| sessionManager.restoreSessionsToUnhandledBranchBuffer(true,unhandledBranchBuffer) | |||
| sessionManager.restoreSessionsToUnhandledBranchBuffer(false,unhandledBranchBuffer) | |||
| if len(unhandledBranchBuffer) > 0 { | |||
| for _,branchSession := range unhandledBranchBuffer { | |||
| found := sessionManager.SessionMap[branchSession.Xid] | |||
| if found == nil { | |||
| logging.Logger.Infof("GlobalSession Does Not Exists For BranchSession [%d/%s]",branchSession.BranchId,branchSession.Xid) | |||
| } else { | |||
| existingBranch := found.GetBranch(branchSession.BranchId) | |||
| if existingBranch == nil { | |||
| found.Add(branchSession) | |||
| } else { | |||
| existingBranch.Status = branchSession.Status | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func (sessionManager *FileBasedSessionManager) restoreSessionsToUnhandledBranchBuffer(isHistory bool,unhandledBranchSessions map[int64]*session.BranchSession) { | |||
| transactionStoreManager, ok := sessionManager.TransactionStoreManager.(ReloadableStore) | |||
| if !ok { | |||
| return | |||
| } | |||
| for { | |||
| if transactionStoreManager.HasRemaining(isHistory) { | |||
| stores := transactionStoreManager.ReadWriteStore(sessionManager.conf.SessionReloadReadSize,isHistory) | |||
| sessionManager.restore(stores,unhandledBranchSessions) | |||
| } else { | |||
| break | |||
| } | |||
| } | |||
| } | |||
| func (sessionManager *FileBasedSessionManager) washSessions() { | |||
| if len(sessionManager.SessionMap) > 0 { | |||
| for _,globalSession := range sessionManager.SessionMap { | |||
| switch globalSession.Status { | |||
| case meta.GlobalStatusUnknown: | |||
| case meta.GlobalStatusCommitted: | |||
| case meta.GlobalStatusCommitFailed: | |||
| case meta.GlobalStatusRollbacked: | |||
| case meta.GlobalStatusRollbackFailed: | |||
| case meta.GlobalStatusTimeoutRollbacked: | |||
| case meta.GlobalStatusTimeoutRollbackFailed: | |||
| case meta.GlobalStatusFinished: | |||
| // Remove all sessions finished | |||
| delete(sessionManager.SessionMap, globalSession.Xid) | |||
| break | |||
| default: | |||
| break | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func (sessionManager *FileBasedSessionManager) restore(stores []*TransactionWriteStore, unhandledBranchSessions map[int64]*session.BranchSession) { | |||
| maxRecoverId := util.UUID | |||
| for _,store := range stores { | |||
| logOperation := store.LogOperation | |||
| sessionStorable := store.SessionRequest | |||
| maxRecoverId = getMaxId(maxRecoverId, sessionStorable) | |||
| switch logOperation { | |||
| case LogOperationGlobalAdd: | |||
| case LogOperationGlobalUpdate: | |||
| { | |||
| globalSession := sessionStorable.(*session.GlobalSession) | |||
| if globalSession.TransactionId == int64(0) { | |||
| logging.Logger.Errorf("Restore globalSession from file failed, the transactionId is zero , xid:%s", globalSession.Xid) | |||
| break | |||
| } | |||
| foundGlobalSession := sessionManager.SessionMap[globalSession.Xid] | |||
| if foundGlobalSession == nil { | |||
| sessionManager.SessionMap[globalSession.Xid] = globalSession | |||
| } else { | |||
| foundGlobalSession.Status = globalSession.Status | |||
| } | |||
| break | |||
| } | |||
| case LogOperationGlobalRemove: | |||
| { | |||
| globalSession := sessionStorable.(*session.GlobalSession) | |||
| if globalSession.TransactionId == int64(0) { | |||
| logging.Logger.Errorf("Restore globalSession from file failed, the transactionId is zero , xid:%s", globalSession.Xid) | |||
| break | |||
| } | |||
| delete(sessionManager.SessionMap, globalSession.Xid) | |||
| break | |||
| } | |||
| case LogOperationBranchAdd: | |||
| case LogOperationBranchUpdate: | |||
| { | |||
| branchSession := sessionStorable.(*session.BranchSession) | |||
| if branchSession.TransactionId == int64(0) { | |||
| logging.Logger.Errorf("Restore branchSession from file failed, the transactionId is zero , xid:%s", branchSession.Xid) | |||
| break | |||
| } | |||
| foundGlobalSession := sessionManager.SessionMap[branchSession.Xid] | |||
| if foundGlobalSession == nil { | |||
| unhandledBranchSessions[branchSession.BranchId] = branchSession | |||
| } else { | |||
| existingBranch := foundGlobalSession.GetBranch(branchSession.BranchId) | |||
| if existingBranch == nil { | |||
| foundGlobalSession.Add(branchSession) | |||
| } else { | |||
| existingBranch.Status = branchSession.Status | |||
| } | |||
| } | |||
| break | |||
| } | |||
| case LogOperationBranchRemove: | |||
| { | |||
| branchSession := sessionStorable.(*session.BranchSession) | |||
| if branchSession.TransactionId == int64(0) { | |||
| logging.Logger.Errorf("Restore branchSession from file failed, the transactionId is zero , xid:%s", branchSession.Xid) | |||
| break | |||
| } | |||
| foundGlobalSession := sessionManager.SessionMap[branchSession.Xid] | |||
| if foundGlobalSession == nil { | |||
| logging.Logger.Infof("GlobalSession To Be Updated (Remove Branch) Does Not Exists [%d/%s]",branchSession.BranchId,branchSession.Xid) | |||
| } else { | |||
| existingBranch := foundGlobalSession.GetBranch(branchSession.BranchId) | |||
| if existingBranch == nil { | |||
| logging.Logger.Infof("BranchSession To Be Updated Does Not Exists [%d/%s]",branchSession.BranchId,branchSession.Xid) | |||
| } else { | |||
| foundGlobalSession.Remove(existingBranch) | |||
| } | |||
| } | |||
| break | |||
| } | |||
| default: | |||
| break | |||
| } | |||
| } | |||
| setMaxId(maxRecoverId) | |||
| } | |||
| func getMaxId(maxRecoverId int64, sessionStorable session.SessionStorable) int64 { | |||
| var currentId int64 = 0 | |||
| var gs, ok1 = sessionStorable.(*session.GlobalSession) | |||
| if ok1 { | |||
| currentId = gs.TransactionId | |||
| } | |||
| var bs, ok2 = sessionStorable.(*session.BranchSession) | |||
| if ok2 { | |||
| currentId = bs.BranchId | |||
| } | |||
| if maxRecoverId > currentId { | |||
| return maxRecoverId | |||
| } else { | |||
| return currentId | |||
| } | |||
| } | |||
| func setMaxId(maxRecoverId int64) { | |||
| var currentId int64 | |||
| // will be recover multi-thread later | |||
| for{ | |||
| currentId = util.UUID | |||
| if currentId < maxRecoverId { | |||
| if util.SetUUID(currentId,maxRecoverId) { | |||
| break | |||
| } | |||
| } | |||
| break | |||
| } | |||
| } | |||
| @@ -0,0 +1,132 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/model" | |||
| "testing" | |||
| ) | |||
| func TestFileBasedSessionManager_AddGlobalSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_FindGlobalSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| expected := sessionManager.FindGlobalSession(gs.Xid) | |||
| assert.NotNil(t,expected) | |||
| assert.Equal(t,gs.TransactionId,expected.TransactionId) | |||
| assert.Equal(t,gs.ApplicationId,expected.ApplicationId) | |||
| assert.Equal(t,gs.TransactionServiceGroup,expected.TransactionServiceGroup) | |||
| assert.Equal(t,gs.TransactionName,expected.TransactionName) | |||
| assert.Equal(t,gs.Status,expected.Status) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_UpdateGlobalSessionStatus(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| gs.Status = meta.GlobalStatusFinished | |||
| sessionManager.UpdateGlobalSessionStatus(gs,meta.GlobalStatusFinished) | |||
| expected := sessionManager.FindGlobalSession(gs.Xid) | |||
| assert.NotNil(t,gs) | |||
| assert.Equal(t,meta.GlobalStatusFinished,expected.Status) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_RemoveGlobalSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| expected := sessionManager.FindGlobalSession(gs.Xid) | |||
| assert.Nil(t,expected) | |||
| } | |||
| func TestFileBasedSessionManager_AddBranchSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| bs := branchSessionProvider(gs) | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.AddBranchSession(gs,bs) | |||
| sessionManager.RemoveBranchSession(gs,bs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_UpdateBranchSessionStatus(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| bs := branchSessionProvider(gs) | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.AddBranchSession(gs,bs) | |||
| sessionManager.UpdateBranchSessionStatus(bs,meta.BranchStatusPhasetwoCommitted) | |||
| sessionManager.RemoveBranchSession(gs,bs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_RemoveBranchSession(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| bs := branchSessionProvider(gs) | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| sessionManager.AddGlobalSession(gs) | |||
| sessionManager.AddBranchSession(gs,bs) | |||
| sessionManager.RemoveBranchSession(gs,bs) | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| func TestFileBasedSessionManager_AllSessions(t *testing.T) { | |||
| gss := globalSessionsProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| for _,gs := range gss { | |||
| sessionManager.AddGlobalSession(gs) | |||
| } | |||
| allGs := sessionManager.AllSessions() | |||
| assert.NotNil(t,allGs) | |||
| assert.Equal(t,2,len(allGs)) | |||
| for _,gs := range gss { | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| allGs2 := sessionManager.AllSessions() | |||
| assert.Equal(t,0,len(allGs2)) | |||
| } | |||
| func TestFileBasedSessionManager_FindGlobalSessionTest(t *testing.T) { | |||
| gss := globalSessionsProvider() | |||
| sessionManager := NewFileBasedSessionManager("root.data") | |||
| for _,gs := range gss { | |||
| sessionManager.AddGlobalSession(gs) | |||
| } | |||
| sessionCondition := model.SessionCondition{ | |||
| OverTimeAliveMills: 30 * 24 * 3600, | |||
| } | |||
| expectedGlobalSessions := sessionManager.FindGlobalSessions(sessionCondition) | |||
| assert.NotNil(t,expectedGlobalSessions) | |||
| assert.Equal(t,2,len(expectedGlobalSessions)) | |||
| for _,gs := range gss { | |||
| sessionManager.RemoveGlobalSession(gs) | |||
| } | |||
| } | |||
| @@ -0,0 +1,232 @@ | |||
| package holder | |||
| import ( | |||
| "os" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/tc/model" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "strings" | |||
| "sync" | |||
| "sync/atomic" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| var FileTrxNum int64 = 0 | |||
| var PerFileBlockSize int64 = 65535 * 8 | |||
| var HisDataFilenamePostfix = ".1" | |||
| var MaxTrxTimeoutMills int64 = 30 * 60 * 1000 | |||
| type ReloadableStore interface { | |||
| /** | |||
| * Read write holder. | |||
| * | |||
| * @param readSize the read size | |||
| * @param isHistory the is history | |||
| * @return the list | |||
| */ | |||
| ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore | |||
| /** | |||
| * Has remaining boolean. | |||
| * | |||
| * @param isHistory the is history | |||
| * @return the boolean | |||
| */ | |||
| HasRemaining(isHistory bool) bool | |||
| } | |||
| type FileTransactionStoreManager struct { | |||
| SessionManager ISessionManager | |||
| currFullFileName string | |||
| hisFullFileName string | |||
| currFileChannel *os.File | |||
| LastModifiedTime int64 | |||
| TrxStartTimeMills int64 | |||
| sync.Mutex | |||
| recoverHisOffset int64 | |||
| recoverCurrOffset int64 | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) InitFile(fullFileName string) { | |||
| storeManager.currFullFileName = fullFileName | |||
| storeManager.hisFullFileName = fullFileName + HisDataFilenamePostfix | |||
| storeManager.currFileChannel,_ = os.OpenFile(fullFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) | |||
| storeManager.TrxStartTimeMills = int64(util.CurrentTimeMillis()) | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) writeDataFrame(data []byte) { | |||
| dataLength := uint32(len(data)) | |||
| dataLengthBytes := [4]byte{ | |||
| byte(dataLength >> 24), | |||
| byte(dataLength >> 16), | |||
| byte(dataLength >> 8), | |||
| byte(dataLength), | |||
| } | |||
| storeManager.currFileChannel.Write(dataLengthBytes[:4]) | |||
| storeManager.currFileChannel.Write(data) | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) WriteSession(logOperation LogOperation, session session.SessionStorable) bool { | |||
| storeManager.Lock() | |||
| defer storeManager.Unlock() | |||
| var curFileTrxNum int64 = 0 | |||
| data, err := (&TransactionWriteStore{ | |||
| SessionRequest: session, | |||
| LogOperation: logOperation, | |||
| }).Encode() | |||
| if err != nil { | |||
| logging.Logger.Info(err.Error()) | |||
| return false | |||
| } | |||
| storeManager.writeDataFrame(data) | |||
| storeManager.LastModifiedTime = int64(util.CurrentTimeMillis()) | |||
| curFileTrxNum = atomic.AddInt64(&FileTrxNum,1) | |||
| if curFileTrxNum %PerFileBlockSize == 0 && | |||
| int64(util.CurrentTimeMillis()) - storeManager.TrxStartTimeMills > MaxTrxTimeoutMills { | |||
| storeManager.saveHistory() | |||
| } | |||
| return true | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) ReadSession(xid string) *session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) Shutdown() { | |||
| storeManager.currFileChannel.Close() | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) GetCurrentMaxSessionId() int64{ | |||
| return int64(0) | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) saveHistory() { | |||
| storeManager.findTimeoutAndSave() | |||
| os.Rename(storeManager.currFullFileName,storeManager.hisFullFileName) | |||
| storeManager.InitFile(storeManager.currFullFileName) | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) findTimeoutAndSave() (bool,error) { | |||
| globalSessionsOverMaxTimeout := storeManager.SessionManager.FindGlobalSessions(model.SessionCondition{OverTimeAliveMills: MaxTrxTimeoutMills}) | |||
| if globalSessionsOverMaxTimeout == nil { | |||
| return true,nil | |||
| } | |||
| for _, globalSession := range globalSessionsOverMaxTimeout { | |||
| globalWriteStore := &TransactionWriteStore{ | |||
| SessionRequest: globalSession, | |||
| LogOperation: LogOperationGlobalAdd, | |||
| } | |||
| data,err := globalWriteStore.Encode() | |||
| if err != nil { | |||
| return false,err | |||
| } | |||
| storeManager.writeDataFrame(data) | |||
| branchSessIonsOverMaXTimeout := globalSession.GetSortedBranches() | |||
| if len(branchSessIonsOverMaXTimeout) > 0 { | |||
| for _,branchSession := range branchSessIonsOverMaXTimeout { | |||
| branchWriteStore := &TransactionWriteStore{ | |||
| SessionRequest: branchSession, | |||
| LogOperation: LogOperationBranchAdd, | |||
| } | |||
| data,err := branchWriteStore.Encode() | |||
| if err != nil { | |||
| return false,err | |||
| } | |||
| storeManager.writeDataFrame(data) | |||
| } | |||
| } | |||
| } | |||
| return true,nil | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore { | |||
| var ( | |||
| file *os.File | |||
| currentOffset int64 | |||
| ) | |||
| if isHistory { | |||
| file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777) | |||
| currentOffset = storeManager.recoverHisOffset | |||
| } else { | |||
| file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777) | |||
| currentOffset = storeManager.recoverCurrOffset | |||
| } | |||
| return storeManager.parseDataFile(file,readSize,currentOffset) | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) HasRemaining(isHistory bool) bool { | |||
| var ( | |||
| file *os.File | |||
| currentOffset int64 | |||
| ) | |||
| if isHistory { | |||
| file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777) | |||
| currentOffset = storeManager.recoverHisOffset | |||
| } else { | |||
| file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777) | |||
| currentOffset = storeManager.recoverCurrOffset | |||
| } | |||
| defer file.Close() | |||
| fi,_ := file.Stat() | |||
| return currentOffset < fi.Size() | |||
| } | |||
| func (storeManager * FileTransactionStoreManager) parseDataFile(file *os.File, readSize int, currentOffset int64) []*TransactionWriteStore { | |||
| defer file.Close() | |||
| var result = make([]*TransactionWriteStore,0) | |||
| fi,_ := file.Stat() | |||
| fileSize := fi.Size() | |||
| reader := byteio.BigEndianReader{Reader:file} | |||
| offset := currentOffset | |||
| for { | |||
| if offset >= fileSize { | |||
| break | |||
| } | |||
| file.Seek(offset, 0) | |||
| dataLength, n, _ := reader.ReadUint32() | |||
| if n < 4 { | |||
| break | |||
| } | |||
| data := make([]byte, int(dataLength)) | |||
| length, _ := reader.Read(data) | |||
| offset += int64(length + 4) | |||
| if length == int(dataLength) { | |||
| st := &TransactionWriteStore{} | |||
| st.Decode(data) | |||
| result = append(result, st) | |||
| if len(result) == readSize { | |||
| break | |||
| } | |||
| } else if length == 0 { | |||
| break | |||
| } | |||
| } | |||
| if isHisFile(fi.Name()) { | |||
| storeManager.recoverHisOffset = offset | |||
| } else { | |||
| storeManager.recoverCurrOffset = offset | |||
| } | |||
| return result | |||
| } | |||
| func isHisFile(path string) bool { | |||
| return strings.HasSuffix(path,HisDataFilenamePostfix) | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/tc/lock" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| ) | |||
| type SessionHolder struct { | |||
| RootSessionManager ISessionManager | |||
| AsyncCommittingSessionManager ISessionManager | |||
| RetryCommittingSessionManager ISessionManager | |||
| RetryRollbackingSessionManager ISessionManager | |||
| } | |||
| var sessionHolder SessionHolder | |||
| func init() { | |||
| sessionHolder = SessionHolder{ | |||
| RootSessionManager: NewFileBasedSessionManager(config.GetDefaultFileStoreConfig()), | |||
| AsyncCommittingSessionManager: NewDefaultSessionManager("default"), | |||
| RetryCommittingSessionManager: NewDefaultSessionManager("default"), | |||
| RetryRollbackingSessionManager: NewDefaultSessionManager("default"), | |||
| } | |||
| sessionHolder.reload() | |||
| } | |||
| func GetSessionHolder() SessionHolder { | |||
| return sessionHolder | |||
| } | |||
| func (sessionHolder SessionHolder) FindGlobalSession(xid string) *session.GlobalSession { | |||
| return sessionHolder.FindGlobalSessionWithBranchSessions(xid, true) | |||
| } | |||
| func (sessionHolder SessionHolder) FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession { | |||
| return sessionHolder.RootSessionManager.FindGlobalSessionWithBranchSessions(xid, withBranchSessions) | |||
| } | |||
| func (sessionHolder SessionHolder) reload() { | |||
| sessionManager, reloadable := sessionHolder.RootSessionManager.(Reloadable) | |||
| if reloadable { | |||
| sessionManager.Reload() | |||
| reloadedSessions := sessionHolder.RootSessionManager.AllSessions() | |||
| if reloadedSessions != nil && len(reloadedSessions) > 0 { | |||
| for _,globalSession := range reloadedSessions { | |||
| switch globalSession.Status { | |||
| case meta.GlobalStatusUnknown: | |||
| case meta.GlobalStatusCommitted: | |||
| case meta.GlobalStatusCommitFailed: | |||
| case meta.GlobalStatusRollbacked: | |||
| case meta.GlobalStatusRollbackFailed: | |||
| case meta.GlobalStatusTimeoutRollbacked: | |||
| case meta.GlobalStatusTimeoutRollbackFailed: | |||
| case meta.GlobalStatusFinished: | |||
| logging.Logger.Errorf("Reloaded Session should NOT be %s",globalSession.Status.String()) | |||
| break | |||
| case meta.GlobalStatusAsyncCommitting: | |||
| sessionHolder.AsyncCommittingSessionManager.AddGlobalSession(globalSession) | |||
| break | |||
| default: | |||
| branchSessions := globalSession.GetSortedBranches() | |||
| for _,branchSession := range branchSessions { | |||
| lock.GetLockManager().AcquireLock(branchSession) | |||
| } | |||
| switch globalSession.Status { | |||
| case meta.GlobalStatusCommitting: | |||
| case meta.GlobalStatusCommitRetrying: | |||
| sessionHolder.RetryCommittingSessionManager.AddGlobalSession(globalSession) | |||
| break | |||
| case meta.GlobalStatusRollbacking: | |||
| case meta.GlobalStatusRollbackRetrying: | |||
| case meta.GlobalStatusTimeoutRollbacking: | |||
| case meta.GlobalStatusTimeoutRollbackRetrying: | |||
| sessionHolder.RetryRollbackingSessionManager.AddGlobalSession(globalSession) | |||
| break | |||
| case meta.GlobalStatusBegin: | |||
| globalSession.SetActive(true) | |||
| break | |||
| default: | |||
| logging.Logger.Errorf("NOT properly handled %s",globalSession.Status) | |||
| break | |||
| } | |||
| break | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,181 @@ | |||
| package holder | |||
| import ( | |||
| "errors" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/model" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| ) | |||
| type ISessionManager interface { | |||
| /** | |||
| * Add global session. | |||
| * | |||
| * @param session the session | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| AddGlobalSession(session *session.GlobalSession) error | |||
| /** | |||
| * Find global session global session. | |||
| * | |||
| * @param xid the xid | |||
| * @return the global session | |||
| */ | |||
| FindGlobalSession(xid string) *session.GlobalSession | |||
| /** | |||
| * Find global session global session. | |||
| * | |||
| * @param xid the xid | |||
| * @param withBranchSessions the withBranchSessions | |||
| * @return the global session | |||
| */ | |||
| FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession | |||
| /** | |||
| * Update global session status. | |||
| * | |||
| * @param session the session | |||
| * @param status the status | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| UpdateGlobalSessionStatus(session *session.GlobalSession, status meta.GlobalStatus) error | |||
| /** | |||
| * Remove global session. | |||
| * | |||
| * @param session the session | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| RemoveGlobalSession(session *session.GlobalSession) error | |||
| /** | |||
| * Add branch session. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param session the session | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| AddBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error | |||
| /** | |||
| * Update branch session status. | |||
| * | |||
| * @param session the session | |||
| * @param status the status | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| UpdateBranchSessionStatus(session *session.BranchSession, status meta.BranchStatus) error | |||
| /** | |||
| * Remove branch session. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param session the session | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| RemoveBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error | |||
| /** | |||
| * All sessions collection. | |||
| * | |||
| * @return the collection | |||
| */ | |||
| AllSessions() []*session.GlobalSession | |||
| /** | |||
| * Find global sessions list. | |||
| * | |||
| * @param condition the condition | |||
| * @return the list | |||
| */ | |||
| FindGlobalSessions(condition model.SessionCondition) []*session.GlobalSession | |||
| } | |||
| type AbstractSessionManager struct { | |||
| TransactionStoreManager ITransactionStoreManager | |||
| Name string | |||
| } | |||
| func (sessionManager *AbstractSessionManager) AddGlobalSession(session *session.GlobalSession) error{ | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalAdd.String()) | |||
| sessionManager.writeSession(LogOperationGlobalAdd,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) UpdateGlobalSessionStatus(session *session.GlobalSession, status meta.GlobalStatus) error { | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalUpdate.String()) | |||
| sessionManager.writeSession(LogOperationGlobalUpdate,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) RemoveGlobalSession(session *session.GlobalSession) error{ | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalRemove.String()) | |||
| sessionManager.writeSession(LogOperationGlobalRemove,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) AddBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error{ | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchAdd.String()) | |||
| sessionManager.writeSession(LogOperationBranchAdd,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) UpdateBranchSessionStatus(session *session.BranchSession, status meta.BranchStatus) error{ | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchUpdate.String()) | |||
| sessionManager.writeSession(LogOperationBranchUpdate,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) RemoveBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error{ | |||
| logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchRemove.String()) | |||
| sessionManager.writeSession(LogOperationBranchRemove,session) | |||
| return nil | |||
| } | |||
| func (sessionManager *AbstractSessionManager) writeSession(logOperation LogOperation, sessionStorable session.SessionStorable) error { | |||
| result := sessionManager.TransactionStoreManager.WriteSession(logOperation,sessionStorable) | |||
| if !result { | |||
| if logOperation == LogOperationGlobalAdd { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to holder global session", | |||
| } | |||
| } | |||
| if logOperation == LogOperationGlobalUpdate { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to update global session", | |||
| } | |||
| } | |||
| if logOperation == LogOperationGlobalRemove { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to remove global session", | |||
| } | |||
| } | |||
| if logOperation == LogOperationBranchAdd { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to holder branch session", | |||
| } | |||
| } | |||
| if logOperation == LogOperationBranchUpdate { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to update branch session", | |||
| } | |||
| } | |||
| if logOperation == LogOperationBranchRemove { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeFailedWriteSession, | |||
| Message: "Fail to remove branch session", | |||
| } | |||
| } | |||
| return errors.New("Unknown LogOperation:"+logOperation.String()) | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,135 @@ | |||
| package holder | |||
| import ( | |||
| "fmt" | |||
| "github.com/dk-lockdown/seata-golang/tc/model" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| ) | |||
| type LogOperation byte | |||
| const ( | |||
| LogOperationGlobalAdd LogOperation = iota | |||
| /** | |||
| * Global update log operation. | |||
| */ | |||
| LogOperationGlobalUpdate | |||
| /** | |||
| * Global remove log operation. | |||
| */ | |||
| LogOperationGlobalRemove | |||
| /** | |||
| * Branch add log operation. | |||
| */ | |||
| LogOperationBranchAdd | |||
| /** | |||
| * Branch update log operation. | |||
| */ | |||
| LogOperationBranchUpdate | |||
| /** | |||
| * Branch remove log operation. | |||
| */ | |||
| LogOperationBranchRemove | |||
| ) | |||
| func (t LogOperation) String() string { | |||
| switch t { | |||
| case LogOperationGlobalAdd: | |||
| return "GlobalAdd" | |||
| case LogOperationGlobalUpdate: | |||
| return "GlobalUpdate" | |||
| case LogOperationGlobalRemove: | |||
| return "GlobalRemove" | |||
| case LogOperationBranchAdd: | |||
| return "BranchAdd" | |||
| case LogOperationBranchUpdate: | |||
| return "BranchUpdate" | |||
| case LogOperationBranchRemove: | |||
| return "BranchRemove" | |||
| default: | |||
| return fmt.Sprintf("%d", t) | |||
| } | |||
| } | |||
| type ITransactionStoreManager interface { | |||
| /** | |||
| * Write session boolean. | |||
| * | |||
| * @param logOperation the log operation | |||
| * @param session the session | |||
| * @return the boolean | |||
| */ | |||
| WriteSession(logOperation LogOperation, session session.SessionStorable) bool | |||
| /** | |||
| * Read global session global session. | |||
| * | |||
| * @param xid the xid | |||
| * @return the global session | |||
| */ | |||
| ReadSession(xid string) *session.GlobalSession | |||
| /** | |||
| * Read session global session. | |||
| * | |||
| * @param xid the xid | |||
| * @param withBranchSessions the withBranchSessions | |||
| * @return the global session | |||
| */ | |||
| ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession | |||
| /** | |||
| * Read session by status list. | |||
| * | |||
| * @param sessionCondition the session condition | |||
| * @return the list | |||
| */ | |||
| ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession | |||
| /** | |||
| * Shutdown. | |||
| */ | |||
| Shutdown() | |||
| /** | |||
| * Gets current max session id. | |||
| * | |||
| * @return the current max session id | |||
| */ | |||
| GetCurrentMaxSessionId() int64 | |||
| } | |||
| type AbstractTransactionStoreManager struct { | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) WriteSession(logOperation LogOperation, session session.SessionStorable) bool { | |||
| return true | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) ReadSession(xid string) *session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession { | |||
| return nil | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) Shutdown() { | |||
| } | |||
| func (transactionStoreManager *AbstractTransactionStoreManager) GetCurrentMaxSessionId() int64 { | |||
| return 0 | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| package holder | |||
| import ( | |||
| "github.com/pkg/errors" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| ) | |||
| type TransactionWriteStore struct { | |||
| SessionRequest session.SessionStorable | |||
| LogOperation LogOperation | |||
| } | |||
| func (transactionWriteStore *TransactionWriteStore) Encode() ([]byte, error){ | |||
| bySessionRequest,err := transactionWriteStore.SessionRequest.Encode() | |||
| if err != nil { | |||
| return nil,err | |||
| } | |||
| byOpCode := transactionWriteStore.LogOperation | |||
| var result = make([]byte,0) | |||
| result = append(result,bySessionRequest...) | |||
| result = append(result,byte(byOpCode)) | |||
| return result,nil | |||
| } | |||
| func (transactionWriteStore *TransactionWriteStore) Decode(src []byte) { | |||
| bySessionRequest := src[:len(src)-1] | |||
| byOpCode := src[len(src)-1:] | |||
| transactionWriteStore.LogOperation = LogOperation(byOpCode[0]) | |||
| sessionRequest, _ := transactionWriteStore.getSessionInstanceByOperation() | |||
| sessionRequest.Decode(bySessionRequest) | |||
| transactionWriteStore.SessionRequest = sessionRequest | |||
| } | |||
| func (transactionWriteStore *TransactionWriteStore) getSessionInstanceByOperation() (session.SessionStorable,error) { | |||
| var sessionStorable session.SessionStorable | |||
| switch transactionWriteStore.LogOperation { | |||
| case LogOperationGlobalAdd: | |||
| case LogOperationGlobalUpdate: | |||
| case LogOperationGlobalRemove: | |||
| sessionStorable = session.NewGlobalSession() | |||
| break | |||
| case LogOperationBranchAdd: | |||
| case LogOperationBranchUpdate: | |||
| case LogOperationBranchRemove: | |||
| sessionStorable = session.NewBranchSession() | |||
| break | |||
| default: | |||
| return nil,errors.New("incorrect logOperation.") | |||
| } | |||
| return sessionStorable,nil | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| package lock | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| ) | |||
| type ILockManager interface { | |||
| /** | |||
| * Acquire lock boolean. | |||
| * | |||
| * @param branchSession the branch session | |||
| * @return the boolean | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| AcquireLock(branchSession *session.BranchSession) (bool, error) | |||
| /** | |||
| * Un lock boolean. | |||
| * | |||
| * @param branchSession the branch session | |||
| * @return the boolean | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| ReleaseLock(branchSession *session.BranchSession) (bool, error) | |||
| /** | |||
| * GlobalSession 是没有锁的,所有的锁都在 BranchSession 上,因为 BranchSession 才 | |||
| * 持有资源,释放 GlobalSession 锁是指释放它所有的 BranchSession 上的锁 | |||
| * Un lock boolean. | |||
| * | |||
| * @param globalSession the global session | |||
| * @return the boolean | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| ReleaseGlobalSessionLock(globalSession *session.GlobalSession) (bool, error) | |||
| /** | |||
| * Is lockable boolean. | |||
| * | |||
| * @param xid the xid | |||
| * @param resourceId the resource id | |||
| * @param lockKey the lock key | |||
| * @return the boolean | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| IsLockable(xid string, resourceId string, lockKey string) bool | |||
| /** | |||
| * Clean all locks. | |||
| * | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| CleanAllLocks() | |||
| GetLockKeyCount() int64 | |||
| } | |||
| @@ -0,0 +1,152 @@ | |||
| package lock | |||
| import ( | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/dk-lockdown/seata-golang/common" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "sync" | |||
| "testing" | |||
| ) | |||
| func TestLockManager_AcquireLock(t *testing.T) { | |||
| bs := branchSessionProvider() | |||
| ok,err := GetLockManager().AcquireLock(bs) | |||
| assert.Equal(t,ok,true) | |||
| assert.Equal(t,err,nil) | |||
| } | |||
| func TestLockManager_IsLockable(t *testing.T) { | |||
| transId := util.GeneratorUUID() | |||
| ok := GetLockManager().IsLockable(common.XID.GenerateXID(transId),"tb_1","tb_1:13") | |||
| assert.Equal(t,ok,true) | |||
| } | |||
| func TestLockManager_AcquireLock_Fail(t *testing.T) { | |||
| sessions := branchSessionsProvider() | |||
| result1,err1 := GetLockManager().AcquireLock(sessions[0]) | |||
| result2,err2 := GetLockManager().AcquireLock(sessions[1]) | |||
| assert.True(t,result1) | |||
| assert.Equal(t,err1,nil) | |||
| assert.False(t,result2) | |||
| assert.Equal(t,err2,nil) | |||
| } | |||
| func TestLockManager_AcquireLock_DeadLock(t *testing.T) { | |||
| sessions := deadlockBranchSessionsProvider() | |||
| defer func() { | |||
| GetLockManager().ReleaseLock(sessions[0]) | |||
| GetLockManager().ReleaseLock(sessions[1]) | |||
| }() | |||
| wg := sync.WaitGroup{} | |||
| wg.Add(2) | |||
| go func(session *session.BranchSession) { | |||
| defer wg.Done() | |||
| result, err := GetLockManager().AcquireLock(session) | |||
| logging.Logger.Infof("1: %v %v",result,err) | |||
| }(sessions[0]) | |||
| go func(session *session.BranchSession) { | |||
| defer wg.Done() | |||
| result, err := GetLockManager().AcquireLock(session) | |||
| logging.Logger.Infof("2: %v %v",result,err) | |||
| }(sessions[1]) | |||
| wg.Wait() | |||
| assert.True(t,true) | |||
| } | |||
| func TestLockManager_IsLockable2(t *testing.T) { | |||
| bs := branchSessionProvider() | |||
| bs.SetLockKey("t:4") | |||
| result1 := GetLockManager().IsLockable(bs.Xid,bs.ResourceId,bs.LockKey) | |||
| assert.True(t,result1) | |||
| GetLockManager().AcquireLock(bs) | |||
| bs.SetTransactionId(util.GeneratorUUID()) | |||
| result2 := GetLockManager().IsLockable(bs.Xid,bs.ResourceId,bs.LockKey) | |||
| assert.False(t,result2) | |||
| } | |||
| func TestLockManager_AcquireLock_SessionHolder(t *testing.T) { | |||
| sessions := duplicatePkBranchSessionsProvider() | |||
| result1, _ := GetLockManager().AcquireLock(sessions[0]) | |||
| assert.True(t,result1) | |||
| assert.Equal(t,int64(4),GetLockManager().GetLockKeyCount()) | |||
| result2, _ := GetLockManager().ReleaseLock(sessions[0]) | |||
| assert.True(t,result2) | |||
| assert.Equal(t,int64(0),GetLockManager().GetLockKeyCount()) | |||
| result3, _ := GetLockManager().AcquireLock(sessions[1]) | |||
| assert.True(t,result3) | |||
| assert.Equal(t,int64(4),GetLockManager().GetLockKeyCount()) | |||
| result4, _ := GetLockManager().ReleaseLock(sessions[1]) | |||
| assert.True(t,result4) | |||
| assert.Equal(t,int64(0),GetLockManager().GetLockKeyCount()) | |||
| } | |||
| func deadlockBranchSessionsProvider() []*session.BranchSession { | |||
| return baseBranchSessionsProvider("tb_2", "t:1,2,3,4,5", "t:5,4,3,2,1") | |||
| } | |||
| func duplicatePkBranchSessionsProvider() []*session.BranchSession { | |||
| return baseBranchSessionsProvider("tb_2", "t:1,2;t1:1;t2:2", "t:1,2;t1:1;t2:2") | |||
| } | |||
| func branchSessionsProvider() []*session.BranchSession { | |||
| return baseBranchSessionsProvider("tb_1", "t:1,2", "t:1,2") | |||
| } | |||
| func baseBranchSessionsProvider(resourceId string, lockKey1 string, lockKey2 string) []*session.BranchSession { | |||
| var branchSessions = make([]*session.BranchSession,0) | |||
| transId := util.GeneratorUUID() | |||
| transId2 := util.GeneratorUUID() | |||
| bs := session.NewBranchSession(). | |||
| SetXid(common.XID.GenerateXID(transId)). | |||
| SetTransactionId(transId). | |||
| SetBranchId(1). | |||
| SetResourceGroupId("my_test_tx_group"). | |||
| SetResourceId(resourceId). | |||
| SetLockKey(lockKey1). | |||
| SetBranchType(meta.BranchTypeAT). | |||
| SetStatus(meta.BranchStatusUnknown). | |||
| SetClientId("c1"). | |||
| SetApplicationData([]byte("{\"data\":\"test\"}")) | |||
| bs1 := session.NewBranchSession(). | |||
| SetXid(common.XID.GenerateXID(transId2)). | |||
| SetTransactionId(transId2). | |||
| SetBranchId(1). | |||
| SetResourceGroupId("my_test_tx_group"). | |||
| SetResourceId(resourceId). | |||
| SetLockKey(lockKey2). | |||
| SetBranchType(meta.BranchTypeAT). | |||
| SetStatus(meta.BranchStatusUnknown). | |||
| SetClientId("c1"). | |||
| SetApplicationData([]byte("{\"data\":\"test\"}")) | |||
| branchSessions = append(branchSessions,bs) | |||
| branchSessions = append(branchSessions,bs1) | |||
| return branchSessions | |||
| } | |||
| func branchSessionProvider() *session.BranchSession { | |||
| common.XID.IpAddress="127.0.0.1" | |||
| common.XID.Port=9876 | |||
| transId := util.GeneratorUUID() | |||
| bs := session.NewBranchSession(). | |||
| SetXid(common.XID.GenerateXID(transId)). | |||
| SetTransactionId(transId). | |||
| SetBranchId(1). | |||
| SetResourceGroupId("my_test_tx_group"). | |||
| SetResourceId("tb_1"). | |||
| SetLockKey("tb_1:13"). | |||
| SetBranchType(meta.BranchTypeAT). | |||
| SetStatus(meta.BranchStatusUnknown). | |||
| SetClientId("c1"). | |||
| SetApplicationData([]byte("{\"data\":\"test\"}")) | |||
| return bs | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package lock | |||
| import "sync" | |||
| var lockManager ILockManager | |||
| func init() { | |||
| lockManager = &MemoryLocker{ | |||
| LockMap: &sync.Map{}, | |||
| BucketHolder: &sync.Map{}, | |||
| } | |||
| } | |||
| func GetLockManager() ILockManager { | |||
| return lockManager | |||
| } | |||
| @@ -0,0 +1,188 @@ | |||
| package lock | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/model" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "github.com/pkg/errors" | |||
| "strconv" | |||
| "sync" | |||
| "sync/atomic" | |||
| ) | |||
| const BucketPerTable = 128 | |||
| type MemoryLocker struct { | |||
| LockMap *sync.Map | |||
| // 高流量下,锁资源越多,BucketHolder 的性能越下降 | |||
| BucketHolder *sync.Map | |||
| LockKeyCount int64 | |||
| } | |||
| func (ml *MemoryLocker) AcquireLock(branchSession *session.BranchSession) (bool, error) { | |||
| if branchSession == nil { | |||
| logging.Logger.Errorf("branchSession can't be null for memory/file locker.") | |||
| return false, errors.New("branchSession can't be null for memory/file locker.") | |||
| } | |||
| lockKey := branchSession.LockKey | |||
| if lockKey == "" { | |||
| return true,nil | |||
| } | |||
| locks := collectRowLocksByBranchSession(branchSession) | |||
| if locks == nil { return true,nil } | |||
| return ml.acquireLockByRowLocks(branchSession,locks) | |||
| } | |||
| func (ml *MemoryLocker) ReleaseLock(branchSession *session.BranchSession) (bool, error) { | |||
| if branchSession == nil { | |||
| logging.Logger.Info("branchSession can't be null for memory/file locker.") | |||
| return false,errors.New("branchSession can't be null for memory/file locker") | |||
| } | |||
| locks := collectRowLocksByBranchSession(branchSession) | |||
| return ml.releaseLockByRowLocks(branchSession,locks) | |||
| } | |||
| func (ml *MemoryLocker) ReleaseGlobalSessionLock(globalSession *session.GlobalSession) (bool, error) { | |||
| branchSessions := globalSession.GetSortedBranches() | |||
| releaseLockResult := true | |||
| for _,branchSession := range branchSessions { | |||
| ok, err := ml.ReleaseLock(branchSession) | |||
| if err != nil { | |||
| return ok,err | |||
| } | |||
| if !ok { releaseLockResult = false } | |||
| } | |||
| return releaseLockResult, nil | |||
| } | |||
| func (ml *MemoryLocker) IsLockable(xid string, resourceId string, lockKey string) bool { | |||
| locks := collectRowLocksByLockKeyResourceIdXid(lockKey, resourceId, xid) | |||
| return ml.isLockableByRowLocks(locks) | |||
| } | |||
| func (ml *MemoryLocker) CleanAllLocks() { | |||
| ml.LockMap = &sync.Map{} | |||
| ml.BucketHolder = &sync.Map{} | |||
| ml.LockKeyCount = 0 | |||
| } | |||
| func (ml *MemoryLocker) GetLockKeyCount() int64 { | |||
| return ml.LockKeyCount | |||
| } | |||
| // AcquireLock 申请锁资源,resourceId -> tableName -> bucketId -> pk -> transactionId | |||
| func (ml *MemoryLocker) acquireLockByRowLocks(branchSession *session.BranchSession,rowLocks []*RowLock) (bool, error) { | |||
| if rowLocks == nil { return true, nil } | |||
| resourceId := branchSession.ResourceId | |||
| transactionId := branchSession.TransactionId | |||
| dbLockMap,_ := ml.LockMap.LoadOrStore(resourceId,&sync.Map{}) | |||
| cDbLockMap := dbLockMap.(*sync.Map) | |||
| for _, rowLock := range rowLocks { | |||
| tableLockMap,_ := cDbLockMap.LoadOrStore(rowLock.TableName,&sync.Map{}) | |||
| cTableLockMap := tableLockMap.(*sync.Map) | |||
| bucketId := util.String(rowLock.Pk) % BucketPerTable | |||
| bucketKey := strconv.Itoa(bucketId) | |||
| bucketLockMap,_ := cTableLockMap.LoadOrStore(bucketKey,&sync.Map{}) | |||
| cBucketLockMap := bucketLockMap.(*sync.Map) | |||
| previousLockTransactionId,loaded := cBucketLockMap.LoadOrStore(rowLock.Pk, transactionId) | |||
| if !loaded { | |||
| //No existing rowLock, and now locked by myself | |||
| keysInHolder,_ := ml.BucketHolder.LoadOrStore(cBucketLockMap, model.NewSet()) | |||
| sKeysInHolder := keysInHolder.(*model.Set) | |||
| sKeysInHolder.Add(rowLock.Pk) | |||
| atomic.AddInt64(&ml.LockKeyCount,1) | |||
| } else if previousLockTransactionId == transactionId { | |||
| // Locked by me before | |||
| continue | |||
| } else { | |||
| logging.Logger.Infof("Global rowLock on [%s:%s] is holding by %d", rowLock.TableName, rowLock.Pk,previousLockTransactionId) | |||
| // branchSession unlock | |||
| _,err := ml.ReleaseLock(branchSession) | |||
| return false,err | |||
| } | |||
| } | |||
| return true, nil | |||
| } | |||
| func (ml *MemoryLocker) releaseLockByRowLocks(branchSession *session.BranchSession,rowLocks []*RowLock) (bool,error) { | |||
| if rowLocks == nil { return false, nil } | |||
| releaseLock := func (key, value interface{}) bool { | |||
| cBucketLockMap := key.(*sync.Map) | |||
| keys := value.(*model.Set) | |||
| for _, key := range keys.List() { | |||
| transId, ok := cBucketLockMap.Load(key) | |||
| if ok && transId == branchSession.TransactionId { | |||
| cBucketLockMap.Delete(key) | |||
| // keys.List() 是一个新的 slice,移除 key 并不会导致错误发生 | |||
| keys.Remove(key) | |||
| atomic.AddInt64(&ml.LockKeyCount,-1) | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| ml.BucketHolder.Range(releaseLock) | |||
| return true, nil | |||
| } | |||
| func (ml *MemoryLocker) isLockableByRowLocks(rowLocks []*RowLock) bool { | |||
| if rowLocks == nil { return true } | |||
| resourceId := rowLocks[0].ResourceId | |||
| transactionId := rowLocks[0].TransactionId | |||
| dbLockMap, ok := ml.LockMap.Load(resourceId) | |||
| if !ok { | |||
| return true | |||
| } | |||
| cDbLockMap := dbLockMap.(*sync.Map) | |||
| for _, rowLock := range rowLocks { | |||
| tableLockMap,ok := cDbLockMap.Load(rowLock.TableName) | |||
| if !ok { | |||
| continue | |||
| } | |||
| cTableLockMap := tableLockMap.(*sync.Map) | |||
| bucketId := util.String(rowLock.Pk) % BucketPerTable | |||
| bucketKey := strconv.Itoa(bucketId) | |||
| bucketLockMap,ok := cTableLockMap.Load(bucketKey) | |||
| if !ok { | |||
| continue | |||
| } | |||
| cBucketLockMap := bucketLockMap.(*sync.Map) | |||
| previousLockTransactionId,ok := cBucketLockMap.Load(rowLock.Pk) | |||
| if !ok || previousLockTransactionId == transactionId { | |||
| // Locked by me before | |||
| continue | |||
| } else { | |||
| logging.Logger.Infof("Global rowLock on [%s:%s] is holding by %d", rowLock.TableName, rowLock.Pk,previousLockTransactionId) | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| package lock | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/common" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "strings" | |||
| ) | |||
| type RowLock struct { | |||
| Xid string | |||
| TransactionId int64 | |||
| BranchId int64 | |||
| ResourceId string | |||
| TableName string | |||
| Pk string | |||
| RowKey string | |||
| Feature string | |||
| } | |||
| func collectRowLocksByBranchSession(branchSession *session.BranchSession) []*RowLock { | |||
| if branchSession == nil || branchSession.LockKey == "" { | |||
| return nil | |||
| } | |||
| return collectRowLocks(branchSession.LockKey,branchSession.ResourceId,branchSession.Xid,branchSession.TransactionId,branchSession.BranchId) | |||
| } | |||
| func collectRowLocksByLockKeyResourceIdXid(lockKey string, | |||
| resourceId string, | |||
| xid string) []*RowLock { | |||
| return collectRowLocks(lockKey,resourceId,xid,common.XID.GetTransactionId(xid),0) | |||
| } | |||
| func collectRowLocks(lockKey string, | |||
| resourceId string, | |||
| xid string, | |||
| transactionId int64, | |||
| branchId int64) []*RowLock { | |||
| var locks = make([]*RowLock,0) | |||
| tableGroupedLockKeys := strings.Split(lockKey,";") | |||
| for _, tableGroupedLockKey := range tableGroupedLockKeys { | |||
| idx := strings.Index(tableGroupedLockKey,":") | |||
| if idx < 0 { return nil } | |||
| tableName := tableGroupedLockKey[0:idx] | |||
| mergedPKs := tableGroupedLockKey[idx+1:] | |||
| if mergedPKs == "" { return nil } | |||
| pks := strings.Split(mergedPKs,",") | |||
| if len(pks) == 0 { return nil } | |||
| for _,pk := range pks { | |||
| if pk != "" { | |||
| rowLock := &RowLock{ | |||
| Xid: xid, | |||
| TransactionId: transactionId, | |||
| BranchId: branchId, | |||
| ResourceId: resourceId, | |||
| TableName: tableName, | |||
| Pk: pk, | |||
| } | |||
| locks = append(locks,rowLock) | |||
| } | |||
| } | |||
| } | |||
| return locks | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| package model | |||
| import "github.com/dk-lockdown/seata-golang/meta" | |||
| type SessionCondition struct{ | |||
| TransactionId int64 | |||
| Xid string | |||
| Status meta.GlobalStatus | |||
| Statuses []meta.GlobalStatus | |||
| OverTimeAliveMills int64 | |||
| } | |||
| @@ -0,0 +1,659 @@ | |||
| package server | |||
| import ( | |||
| "fmt" | |||
| "github.com/dubbogo/getty" | |||
| "github.com/pkg/errors" | |||
| "go.uber.org/atomic" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "github.com/dk-lockdown/seata-golang/protocal/codec" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/tc/event" | |||
| "github.com/dk-lockdown/seata-golang/tc/holder" | |||
| "github.com/dk-lockdown/seata-golang/tc/lock" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| const ( | |||
| RPC_REQUEST_TIMEOUT = 30 * time.Second | |||
| ALWAYS_RETRY_BOUNDARY = 0 | |||
| ) | |||
| // MessageFuture ... | |||
| type MessageFuture struct { | |||
| id int32 | |||
| err error | |||
| response interface{} | |||
| done chan bool | |||
| } | |||
| // NewMessageFuture ... | |||
| func NewMessageFuture(message protocal.RpcMessage) *MessageFuture { | |||
| return &MessageFuture{ | |||
| id: message.Id, | |||
| done: make(chan bool), | |||
| } | |||
| } | |||
| type DefaultCoordinator struct { | |||
| conf config.ServerConfig | |||
| core ITransactionCoordinator | |||
| idGenerator atomic.Uint32 | |||
| futures *sync.Map | |||
| timeoutCheckTicker *time.Ticker | |||
| retryRollbackingTicker *time.Ticker | |||
| retryCommittingTicker *time.Ticker | |||
| asyncCommittingTicker *time.Ticker | |||
| undoLogDeleteTicker *time.Ticker | |||
| } | |||
| func NewDefaultCoordinator(conf config.ServerConfig) *DefaultCoordinator { | |||
| coordinator := &DefaultCoordinator{ | |||
| conf: conf, | |||
| idGenerator: atomic.Uint32{}, | |||
| futures: &sync.Map{}, | |||
| timeoutCheckTicker: time.NewTicker(conf.TimeoutRetryPeriod), | |||
| retryRollbackingTicker: time.NewTicker(conf.RollbackingRetryPeriod), | |||
| retryCommittingTicker: time.NewTicker(conf.CommittingRetryPeriod), | |||
| asyncCommittingTicker: time.NewTicker(conf.AsynCommittingRetryPeriod), | |||
| undoLogDeleteTicker: time.NewTicker(conf.LogDeletePeriod), | |||
| } | |||
| core := NewCore(coordinator) | |||
| coordinator.core = core | |||
| go coordinator.processTimeoutCheck() | |||
| go coordinator.processRetryRollbacking() | |||
| go coordinator.processRetryCommitting() | |||
| go coordinator.processAsyncCommitting() | |||
| go coordinator.processUndoLogDelete() | |||
| return coordinator | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnOpen(session getty.Session) error { | |||
| logging.Logger.Infof("got session:%s", session.Stat()) | |||
| return nil | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnError(session getty.Session, err error) { | |||
| session.Close() | |||
| logging.Logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnClose(session getty.Session) { | |||
| logging.Logger.Info("session{%s} is closing......", session.Stat()) | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnMessage(session getty.Session, pkg interface{}) { | |||
| logging.Logger.Info("received message:{%v}", pkg) | |||
| rpcMessage,ok := pkg.(protocal.RpcMessage) | |||
| if ok { | |||
| _,isRegTM := rpcMessage.Body.(protocal.RegisterTMRequest) | |||
| if isRegTM { | |||
| coordinator.OnRegTmMessage(rpcMessage,session) | |||
| return | |||
| } | |||
| heartBeat,isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage) | |||
| if isHeartBeat && heartBeat == protocal.HeartBeatMessagePing { | |||
| coordinator.OnCheckMessage(rpcMessage,session) | |||
| return | |||
| } | |||
| if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST || | |||
| rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY { | |||
| logging.Logger.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body) | |||
| _,isRegRM := rpcMessage.Body.(protocal.RegisterRMRequest) | |||
| if isRegRM { | |||
| coordinator.OnRegRmMessage(rpcMessage,session) | |||
| } else { | |||
| if SessionManager.IsRegistered(session) { | |||
| coordinator.OnTrxMessage(rpcMessage,session) | |||
| } else { | |||
| session.Close() | |||
| logging.Logger.Infof("close a unhandled connection! [%v]", session) | |||
| } | |||
| } | |||
| } else { | |||
| resp,loaded := coordinator.futures.Load(rpcMessage.Id) | |||
| if loaded { | |||
| response := resp.(*MessageFuture) | |||
| response.response = rpcMessage.Body | |||
| response.done <- true | |||
| coordinator.futures.Delete(rpcMessage.Id) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnCron(session getty.Session) { | |||
| } | |||
| ///////////////////////////////////////////////////////////// | |||
| // ServerMessageListener | |||
| ///////////////////////////////////////////////////////////// | |||
| func (coordinator *DefaultCoordinator) OnTrxMessage(rpcMessage protocal.RpcMessage, session getty.Session) { | |||
| rpcContext := SessionManager.GetContextFromIdentified(session) | |||
| logging.Logger.Debugf("server received:%v,clientIp:%s,vgroup:%s",rpcMessage.Body,session.RemoteAddr(),rpcContext.TransactionServiceGroup) | |||
| warpMessage, isWarpMessage := rpcMessage.Body.(protocal.MergedWarpMessage) | |||
| if isWarpMessage { | |||
| resultMessage := protocal.MergeResultMessage{Msgs:make([]protocal.MessageTypeAware,0)} | |||
| for _,msg := range warpMessage.Msgs { | |||
| resp := coordinator.handleTrxMessage(msg,*rpcContext) | |||
| resultMessage.Msgs = append(resultMessage.Msgs,resp) | |||
| } | |||
| coordinator.SendResponse(rpcMessage,rpcContext.session,resultMessage) | |||
| } else { | |||
| message := rpcMessage.Body.(protocal.MessageTypeAware) | |||
| resp := coordinator.handleTrxMessage(message,*rpcContext) | |||
| coordinator.SendResponse(rpcMessage,rpcContext.session,resp) | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) handleTrxMessage(msg protocal.MessageTypeAware,ctx RpcContext) protocal.MessageTypeAware { | |||
| switch msg.GetTypeCode() { | |||
| case protocal.TypeGlobalBegin: | |||
| req := msg.(protocal.GlobalBeginRequest) | |||
| resp := coordinator.doGlobalBegin(req,ctx) | |||
| return resp | |||
| case protocal.TypeGlobalStatus: | |||
| req := msg.(protocal.GlobalStatusRequest) | |||
| resp := coordinator.doGlobalStatus(req,ctx) | |||
| return resp | |||
| case protocal.TypeGlobalReport: | |||
| req := msg.(protocal.GlobalReportRequest) | |||
| resp := coordinator.doGlobalReport(req,ctx) | |||
| return resp | |||
| case protocal.TypeGlobalCommit: | |||
| req := msg.(protocal.GlobalCommitRequest) | |||
| resp := coordinator.doGlobalCommit(req,ctx) | |||
| return resp | |||
| case protocal.TypeGlobalRollback: | |||
| req := msg.(protocal.GlobalRollbackRequest) | |||
| resp := coordinator.doGlobalRollback(req,ctx) | |||
| return resp | |||
| case protocal.TypeBranchRegister: | |||
| req := msg.(protocal.BranchRegisterRequest) | |||
| resp := coordinator.doBranchRegister(req,ctx) | |||
| return resp | |||
| case protocal.TypeBranchStatusReport: | |||
| req := msg.(protocal.BranchReportRequest) | |||
| resp := coordinator.doBranchReport(req,ctx) | |||
| return resp | |||
| default: | |||
| return nil | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnRegRmMessage(rpcMessage protocal.RpcMessage, session getty.Session) { | |||
| message := rpcMessage.Body.(protocal.RegisterRMRequest) | |||
| //version things | |||
| SessionManager.RegisterRmGettySession(message,session) | |||
| logging.Logger.Debugf("checkAuth for client:%s,vgroup:%s,applicationId:%s",session.RemoteAddr(),message.TransactionServiceGroup,message.ApplicationId) | |||
| coordinator.SendResponse(rpcMessage,session,protocal.RegisterRMResponse{AbstractIdentifyResponse: protocal.AbstractIdentifyResponse{Identified: true}}) | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnRegTmMessage(rpcMessage protocal.RpcMessage, session getty.Session) { | |||
| message := rpcMessage.Body.(protocal.RegisterTMRequest) | |||
| //version things | |||
| SessionManager.RegisterTmGettySession(message,session) | |||
| logging.Logger.Debugf("checkAuth for client:%s,vgroup:%s,applicationId:%s",session.RemoteAddr(),message.TransactionServiceGroup,message.ApplicationId) | |||
| coordinator.SendResponse(rpcMessage,session,protocal.RegisterTMResponse{AbstractIdentifyResponse: protocal.AbstractIdentifyResponse{Identified: true}}) | |||
| } | |||
| func (coordinator *DefaultCoordinator) OnCheckMessage(rpcMessage protocal.RpcMessage, session getty.Session) { | |||
| coordinator.SendResponse(rpcMessage,session,protocal.HeartBeatMessagePong) | |||
| logging.Logger.Debugf("received PING from %s", session.RemoteAddr()) | |||
| } | |||
| ///////////////////////////////////////////////////////////// | |||
| // ServerMessageSender | |||
| ///////////////////////////////////////////////////////////// | |||
| func (coordinator *DefaultCoordinator) SendResponse(request protocal.RpcMessage, session getty.Session, msg interface{}) { | |||
| var ss = session | |||
| _,ok := msg.(protocal.HeartBeatMessage) | |||
| if !ok { | |||
| ss = SessionManager.GetSameClientGettySession(session) | |||
| } | |||
| if ss != nil { | |||
| coordinator.defaultSendResponse(request,ss,msg) | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) SendSyncRequest(resourceId string, clientId string, message interface{}) (interface{},error) { | |||
| return coordinator.SendSyncRequestWithTimeout(resourceId,clientId,message,RPC_REQUEST_TIMEOUT) | |||
| } | |||
| func (coordinator *DefaultCoordinator) SendSyncRequestWithTimeout(resourceId string, clientId string, message interface{}, timeout time.Duration) (interface{},error) { | |||
| session,err := SessionManager.GetGettySession(resourceId,clientId) | |||
| if err != nil { | |||
| return nil, errors.WithStack(err) | |||
| } | |||
| return coordinator.sendAsyncRequestWithResponse("",session,message,timeout) | |||
| } | |||
| func (coordinator *DefaultCoordinator) SendSyncRequestByGettySession(session getty.Session, message interface{}) (interface{},error) { | |||
| return coordinator.SendSyncRequestByGettySessionWithTimeout(session,message,RPC_REQUEST_TIMEOUT) | |||
| } | |||
| func (coordinator *DefaultCoordinator) SendSyncRequestByGettySessionWithTimeout(session getty.Session, message interface{}, timeout time.Duration) (interface{},error) { | |||
| if session == nil { | |||
| return nil,errors.New("rm client is not connected") | |||
| } | |||
| return coordinator.sendAsyncRequestWithResponse("",session,message,timeout) | |||
| } | |||
| func (coordinator *DefaultCoordinator) SendASyncRequest(session getty.Session, message interface{}) error { | |||
| return coordinator.sendAsyncRequestWithoutResponse(session,message) | |||
| } | |||
| func (coordinator *DefaultCoordinator) sendAsyncRequestWithResponse(address string,session getty.Session,msg interface{},timeout time.Duration) (interface{},error) { | |||
| if timeout <= time.Duration(0) { | |||
| return nil,errors.New("timeout should more than 0ms") | |||
| } | |||
| return coordinator.sendAsyncRequest(address,session,msg,timeout) | |||
| } | |||
| func (coordinator *DefaultCoordinator) sendAsyncRequestWithoutResponse(session getty.Session,msg interface{}) error { | |||
| _,err := coordinator.sendAsyncRequest("",session,msg,time.Duration(0)) | |||
| return err | |||
| } | |||
| func (coordinator *DefaultCoordinator) sendAsyncRequest(address string,session getty.Session,msg interface{},timeout time.Duration) (interface{},error) { | |||
| var err error | |||
| if session == nil { | |||
| logging.Logger.Warn("sendAsyncRequestWithResponse nothing, caused by null channel.") | |||
| } | |||
| rpcMessage := protocal.RpcMessage{ | |||
| Id: int32(coordinator.idGenerator.Inc()), | |||
| MessageType: protocal.MSGTYPE_RESQUEST_ONEWAY, | |||
| Codec: codec.SEATA, | |||
| Compressor: 0, | |||
| Body: msg, | |||
| } | |||
| resp := NewMessageFuture(rpcMessage) | |||
| coordinator.futures.Store(rpcMessage.Id, resp) | |||
| //config timeout | |||
| err = session.WritePkg(rpcMessage, 60000) | |||
| if err != nil { | |||
| coordinator.futures.Delete(rpcMessage.Id) | |||
| } | |||
| if timeout > time.Duration(0) { | |||
| select { | |||
| case <-getty.GetTimeWheel().After(timeout): | |||
| coordinator.futures.Delete(rpcMessage.Id) | |||
| return nil, errors.Errorf("wait response timeout,ip:%s,request:%v", address, rpcMessage) | |||
| case <-resp.done: | |||
| err = resp.err | |||
| } | |||
| return resp.response, err | |||
| } | |||
| return nil,err | |||
| } | |||
| func (coordinator *DefaultCoordinator) defaultSendResponse(request protocal.RpcMessage, session getty.Session, msg interface{}) { | |||
| resp := protocal.RpcMessage{ | |||
| Id: request.Id, | |||
| Codec: request.Codec, | |||
| Compressor: request.Compressor, | |||
| Body: msg, | |||
| } | |||
| _,ok := msg.(protocal.HeartBeatMessage) | |||
| if ok { | |||
| resp.MessageType = protocal.MSGTYPE_HEARTBEAT_RESPONSE | |||
| } else { | |||
| resp.MessageType = protocal.MSGTYPE_RESPONSE | |||
| } | |||
| session.WritePkg(resp,time.Duration(0)) | |||
| } | |||
| ///////////////////////////////////////////////////////////// | |||
| // TCInboundHandler | |||
| ///////////////////////////////////////////////////////////// | |||
| func (coordinator *DefaultCoordinator) doGlobalBegin(request protocal.GlobalBeginRequest,ctx RpcContext) protocal.GlobalBeginResponse { | |||
| var resp = protocal.GlobalBeginResponse{} | |||
| xid,err := coordinator.core.Begin(ctx.ApplicationId,ctx.TransactionServiceGroup,request.TransactionName,request.Timeout) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Xid = xid | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doGlobalStatus(request protocal.GlobalStatusRequest,ctx RpcContext) protocal.GlobalStatusResponse { | |||
| var resp = protocal.GlobalStatusResponse{} | |||
| globalStatus,err := coordinator.core.GetStatus(request.Xid) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.GlobalStatus = globalStatus | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doGlobalReport(request protocal.GlobalReportRequest,ctx RpcContext) protocal.GlobalReportResponse { | |||
| var resp = protocal.GlobalReportResponse{} | |||
| globalStatus,err := coordinator.core.GlobalReport(request.Xid,request.GlobalStatus) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.GlobalStatus = globalStatus | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doGlobalCommit(request protocal.GlobalCommitRequest,ctx RpcContext) protocal.GlobalCommitResponse { | |||
| var resp = protocal.GlobalCommitResponse{} | |||
| globalStatus,err := coordinator.core.Commit(request.Xid) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.GlobalStatus = globalStatus | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doGlobalRollback(request protocal.GlobalRollbackRequest,ctx RpcContext) protocal.GlobalRollbackResponse { | |||
| var resp = protocal.GlobalRollbackResponse{} | |||
| globalStatus,err := coordinator.core.Rollback(request.Xid) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| globalSession := holder.GetSessionHolder().FindGlobalSessionWithBranchSessions(request.Xid,false) | |||
| if globalSession == nil { | |||
| resp.GlobalStatus = meta.GlobalStatusFinished | |||
| } else { | |||
| resp.GlobalStatus = globalSession.Status | |||
| } | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.GlobalStatus = globalStatus | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doBranchRegister(request protocal.BranchRegisterRequest,ctx RpcContext) protocal.BranchRegisterResponse { | |||
| var resp = protocal.BranchRegisterResponse{} | |||
| branchId,err := coordinator.core.BranchRegister(request.BranchType,request.ResourceId,ctx.ClientId,request.Xid,request.ApplicationData,request.LockKey) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.BranchId = branchId | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doBranchReport(request protocal.BranchReportRequest,ctx RpcContext) protocal.BranchReportResponse { | |||
| var resp = protocal.BranchReportResponse{} | |||
| err := coordinator.core.BranchReport(request.BranchType,request.Xid,request.BranchId,request.Status,request.ApplicationData) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) doLockCheck(request protocal.GlobalLockQueryRequest,ctx RpcContext) protocal.GlobalLockQueryResponse { | |||
| var resp = protocal.GlobalLockQueryResponse{} | |||
| result, err := coordinator.core.LockQuery(request.BranchType,request.ResourceId,request.Xid,request.LockKey) | |||
| if err != nil { | |||
| trxException, ok := err.(meta.TransactionException) | |||
| resp.ResultCode = protocal.ResultCodeFailed | |||
| if ok { | |||
| resp.TransactionExceptionCode = trxException.Code | |||
| resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error()) | |||
| logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request) | |||
| return resp | |||
| } | |||
| resp.Lockable = result | |||
| resp.ResultCode = protocal.ResultCodeSuccess | |||
| return resp | |||
| } | |||
| func (coordinator *DefaultCoordinator) processTimeoutCheck() { | |||
| for { | |||
| <- coordinator.timeoutCheckTicker.C | |||
| coordinator.timeoutCheck() | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) processRetryRollbacking() { | |||
| for { | |||
| <- coordinator.retryRollbackingTicker.C | |||
| coordinator.handleRetryRollbacking() | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) processRetryCommitting() { | |||
| for { | |||
| <- coordinator.retryCommittingTicker.C | |||
| coordinator.handleRetryCommitting() | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) processAsyncCommitting() { | |||
| for { | |||
| <- coordinator.asyncCommittingTicker.C | |||
| coordinator.handleAsyncCommitting() | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) processUndoLogDelete() { | |||
| for { | |||
| <- coordinator.undoLogDeleteTicker.C | |||
| coordinator.undoLogDelete() | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) timeoutCheck() { | |||
| allSessions := holder.GetSessionHolder().RootSessionManager.AllSessions() | |||
| if allSessions == nil && len(allSessions) <= 0 { | |||
| return | |||
| } | |||
| logging.Logger.Debugf("Transaction Timeout Check Begin: %d",len(allSessions)) | |||
| for _,globalSession := range allSessions { | |||
| logging.Logger.Debugf("%s %s %d %d",globalSession.Xid,globalSession.Status.String(),globalSession.BeginTime,globalSession.Timeout) | |||
| shouldTimout := func (gs *session.GlobalSession) bool { | |||
| globalSession.Lock() | |||
| defer globalSession.Unlock() | |||
| if globalSession.Status != meta.GlobalStatusBegin || !globalSession.IsTimeout() { | |||
| return false | |||
| } | |||
| if globalSession.Active { | |||
| globalSession.Active = false | |||
| } | |||
| changeGlobalSessionStatus(globalSession, meta.GlobalStatusTimeoutRollbacking) | |||
| evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC, globalSession.TransactionName, globalSession.BeginTime, 0, globalSession.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| return true | |||
| }(globalSession) | |||
| if !shouldTimout { | |||
| continue | |||
| } | |||
| logging.Logger.Infof("Global transaction[%s] is timeout and will be rolled back.",globalSession.Status) | |||
| holder.GetSessionHolder().RetryRollbackingSessionManager.AddGlobalSession(globalSession) | |||
| } | |||
| logging.Logger.Debug("Transaction Timeout Check End.") | |||
| } | |||
| func (coordinator *DefaultCoordinator) handleRetryRollbacking() { | |||
| rollbackingSessions := holder.GetSessionHolder().RetryRollbackingSessionManager.AllSessions() | |||
| if rollbackingSessions == nil && len(rollbackingSessions) <= 0 { | |||
| return | |||
| } | |||
| now := util.CurrentTimeMillis() | |||
| for _,rollbackingSession := range rollbackingSessions { | |||
| if rollbackingSession.Status == meta.GlobalStatusRollbacking && !rollbackingSession.IsRollbackingDead() { | |||
| continue | |||
| } | |||
| if isRetryTimeout(int64(now),coordinator.conf.MaxRollbackRetryTimeout,rollbackingSession.BeginTime){ | |||
| if coordinator.conf.RollbackRetryTimeoutUnlockEnable { | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(rollbackingSession) | |||
| } | |||
| holder.GetSessionHolder().RetryRollbackingSessionManager.RemoveGlobalSession(rollbackingSession) | |||
| logging.Logger.Errorf("GlobalSession rollback retry timeout and removed [%s]", rollbackingSession.Xid) | |||
| continue | |||
| } | |||
| _, err := coordinator.core.doGlobalRollback(rollbackingSession,true) | |||
| if err != nil { | |||
| logging.Logger.Infof("Failed to retry rollbacking [%s]",rollbackingSession.Xid) | |||
| } | |||
| } | |||
| } | |||
| func isRetryTimeout(now int64,timeout int64,beginTime int64) bool { | |||
| if timeout >= ALWAYS_RETRY_BOUNDARY && now - beginTime > timeout { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| func (coordinator *DefaultCoordinator) handleRetryCommitting() { | |||
| committingSessions := holder.GetSessionHolder().RetryCommittingSessionManager.AllSessions() | |||
| if committingSessions == nil && len(committingSessions) <= 0 { | |||
| return | |||
| } | |||
| now := util.CurrentTimeMillis() | |||
| for _,committingSession := range committingSessions { | |||
| if isRetryTimeout(int64(now),coordinator.conf.MaxCommitRetryTimeout,committingSession.BeginTime) { | |||
| holder.GetSessionHolder().RetryCommittingSessionManager.RemoveGlobalSession(committingSession) | |||
| logging.Logger.Errorf("GlobalSession commit retry timeout and removed [%s]", committingSession.Xid) | |||
| continue | |||
| } | |||
| _,err := coordinator.core.doGlobalCommit(committingSession,true) | |||
| if err != nil { | |||
| logging.Logger.Infof("Failed to retry committing [%s]",committingSession.Xid) | |||
| } | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) handleAsyncCommitting() { | |||
| asyncCommittingSessions := holder.GetSessionHolder().AsyncCommittingSessionManager.AllSessions() | |||
| if asyncCommittingSessions == nil && len(asyncCommittingSessions) <= 0 { | |||
| return | |||
| } | |||
| for _,asyncCommittingSession := range asyncCommittingSessions { | |||
| if asyncCommittingSession.Status != meta.GlobalStatusAsyncCommitting { | |||
| continue | |||
| } | |||
| _,err := coordinator.core.doGlobalCommit(asyncCommittingSession,true) | |||
| if err != nil { | |||
| logging.Logger.Infof("Failed to async committing [%s]",asyncCommittingSession.Xid) | |||
| } | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) undoLogDelete() { | |||
| saveDays := coordinator.conf.UndoConfig.LogSaveDays | |||
| for key,session := range SessionManager.GetRmSessions() { | |||
| resourceId := key | |||
| deleteRequest := protocal.UndoLogDeleteRequest{ | |||
| ResourceId: resourceId, | |||
| SaveDays: saveDays, | |||
| } | |||
| err := coordinator.SendASyncRequest(session,deleteRequest) | |||
| if err != nil { | |||
| logging.Logger.Errorf("Failed to async delete undo log resourceId = %s", resourceId) | |||
| } | |||
| } | |||
| } | |||
| func (coordinator *DefaultCoordinator) Stop() { | |||
| coordinator.timeoutCheckTicker.Stop() | |||
| coordinator.retryRollbackingTicker.Stop() | |||
| coordinator.retryCommittingTicker.Stop() | |||
| coordinator.asyncCommittingTicker.Stop() | |||
| coordinator.undoLogDeleteTicker.Stop() | |||
| } | |||
| @@ -0,0 +1,584 @@ | |||
| package server | |||
| import ( | |||
| "fmt" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "github.com/dk-lockdown/seata-golang/tc/event" | |||
| "github.com/dk-lockdown/seata-golang/tc/holder" | |||
| "github.com/dk-lockdown/seata-golang/tc/lock" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| ) | |||
| /** | |||
| * +--------------------+-----------------------+--------------------+ | |||
| * | |Method(InBound) |Method(OutBound) | | |||
| * +--------------------+-----------------------+--------------------+ | |||
| * | |Begin | | | |||
| * | |BranchRegister | | | |||
| * | TC |BranchReport | | | |||
| * | (AT&TCC) |(GlobalReport) |branchCommit | | |||
| * | (DefaultCore) |Commit |branchRollback | | |||
| * | |Rollback | | | |||
| * | |GetStatus | | | |||
| * +--------------------+-----------------------+--------------------+ | |||
| * | AT |LockQuery | | | |||
| * +--------------------+-----------------------+--------------------+ | |||
| * | |doGlobalCommit | | | |||
| * | SAGA |doGlobalRollBack | | | |||
| * | |doGlobalReport | | | |||
| * +--------------------+-----------------------+--------------------+ | |||
| * | |||
| * 参考 [effective go 之 Embedding](#https://my.oschina.net/pengfeix/blog/109967) | |||
| * Go does not provide the typical, type-driven notion of subclassing, | |||
| * but it does have the ability to “borrow” pieces of an implementation | |||
| * by embedding types within a struct or interface. | |||
| * Go 没有像其它面向对象语言中的类继承概念,但是,它可以通过在结构体或者接口中嵌入 | |||
| * 其它的类型,来使用被嵌入类型的功能。 | |||
| * | |||
| * 原本 JAVA 版 Seata Sever 设计了 Core 接口,AbstractCore 实现该接口,ATCore、 | |||
| * TccCore、SagaCore 都继承 AbstractCore。使 ATCore、TccCore、SagaCore 每一 | |||
| * 个类单独拿出来都是 Core 接口的实现。但 Go 版的 Seata 我不打算这样设计。我们将 | |||
| * Core 接口里定义的接口方法拿出来,如上面的表格所示,一个全局事务的周期分别对应 Begin、 | |||
| * BranchRegister、BranchReport、Commit、Rollback 接口方法,这些接口方法适用于 | |||
| * AT 模式和 TCC 模式(SAGA 模式暂不了解,先不考虑)。AT 模式会多一个 LockQuery | |||
| * 的接口。另外 OutBound 方向上有两个接口 branchCommit、branchRollback。JAVA 版 | |||
| * 的设计中 doGlobalCommit、doGlobalRollBack、doGlobalReport 其实是私有方法, | |||
| * 这里用首字母小些开头的方法区分。那么 Go 版本的 DefaultCore 设计就出来了(暂不考虑 SAGA), | |||
| * DefaultCore 内嵌入 ATCore。 | |||
| * | |||
| */ | |||
| type AbstractCore struct { | |||
| MessageSender IServerMessageSender | |||
| } | |||
| type ATCore struct { | |||
| AbstractCore | |||
| } | |||
| type SAGACore struct { | |||
| AbstractCore | |||
| } | |||
| type DefaultCore struct { | |||
| AbstractCore | |||
| ATCore | |||
| SAGACore | |||
| coreMap map[meta.BranchType]interface{} | |||
| } | |||
| func NewCore(sender IServerMessageSender) ITransactionCoordinator { | |||
| return &DefaultCore{ | |||
| AbstractCore: AbstractCore{ MessageSender: sender }, | |||
| ATCore: ATCore{}, | |||
| SAGACore: SAGACore{}, | |||
| coreMap: make(map[meta.BranchType]interface{}), | |||
| } | |||
| } | |||
| func (core *ATCore) branchSessionLock(globalSession *session.GlobalSession,branchSession *session.BranchSession) error { | |||
| result,err :=lock.GetLockManager().AcquireLock(branchSession) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !result { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeLockKeyConflict, | |||
| Message: fmt.Sprintf("Global lock acquire failed xid = %s branchId = %s", | |||
| globalSession.Xid,branchSession.BranchId), | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (core *ATCore) branchSessionUnlock(branchSession *session.BranchSession) error { | |||
| _, err := lock.GetLockManager().ReleaseLock(branchSession) | |||
| return err | |||
| } | |||
| func (core *ATCore) LockQuery(branchType meta.BranchType, | |||
| resourceId string, | |||
| xid string, | |||
| lockKeys string) bool { | |||
| return lock.GetLockManager().IsLockable(xid,resourceId,lockKeys) | |||
| } | |||
| func (core *SAGACore) doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error) { | |||
| return true,nil | |||
| } | |||
| func (core *SAGACore) doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error) { | |||
| return true,nil | |||
| } | |||
| func (core *SAGACore) doGlobalReport(globalSession *session.GlobalSession, xid string, param meta.GlobalStatus) error { | |||
| return nil | |||
| } | |||
| func (core *DefaultCore) Begin(applicationId string, transactionServiceGroup string, name string, timeout int32) (string, error) { | |||
| gs := session.NewGlobalSession(). | |||
| SetApplicationId(applicationId). | |||
| SetTransactionServiceGroup(transactionServiceGroup). | |||
| SetTransactionName(name). | |||
| SetTimeout(timeout) | |||
| gs.Begin() | |||
| err := holder.GetSessionHolder().RootSessionManager.AddGlobalSession(gs) | |||
| if err != nil { | |||
| return "",err | |||
| } | |||
| evt := event.NewGlobalTransactionEvent(gs.TransactionId, event.RoleTC,gs.TransactionName,gs.BeginTime,0,gs.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| logging.Logger.Infof("Successfully begin global transaction xid = {}",gs.Xid) | |||
| return gs.Xid, nil | |||
| } | |||
| func (core *DefaultCore) BranchRegister(branchType meta.BranchType, | |||
| resourceId string, | |||
| clientId string, | |||
| xid string, | |||
| applicationData []byte, | |||
| lockKeys string) (int64, error) { | |||
| gs,err := assertGlobalSessionNotNull(xid,false) | |||
| if err != nil { | |||
| return 0,err | |||
| } | |||
| defer gs.Unlock() | |||
| gs.Lock() | |||
| err1 := globalSessionStatusCheck(gs) | |||
| if err1 != nil { | |||
| return 0,err | |||
| } | |||
| bs := session.NewBranchSessionByGlobal(*gs,branchType,resourceId,applicationData,lockKeys,clientId) | |||
| if branchType == meta.BranchTypeAT { | |||
| core.ATCore.branchSessionLock(gs, bs) | |||
| } | |||
| gs.Add(bs) | |||
| logging.Logger.Infof("Successfully register branch xid = %s, branchId = %d",gs.Xid,bs.BranchId) | |||
| return bs.BranchId,nil | |||
| } | |||
| func globalSessionStatusCheck(globalSession *session.GlobalSession) error { | |||
| if !globalSession.Active { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeGlobalTransactionNotActive, | |||
| Message: fmt.Sprintf("Could not register branch into global session xid = %s status = %d",globalSession.Xid,globalSession.Status), | |||
| } | |||
| } | |||
| if globalSession.Status != meta.GlobalStatusBegin { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeGlobalTransactionStatusInvalid, | |||
| Message: fmt.Sprintf("Could not register branch into global session xid = %s status = %d while expecting %d", | |||
| globalSession.Xid,globalSession.Status,meta.GlobalStatusBegin), | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func assertGlobalSessionNotNull(xid string, withBranchSessions bool) (*session.GlobalSession,error) { | |||
| gs := holder.GetSessionHolder().FindGlobalSessionWithBranchSessions(xid,withBranchSessions) | |||
| if gs == nil { | |||
| logging.Logger.Errorf("Could not found global transaction xid = %s",gs.Xid) | |||
| return nil,&meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeGlobalTransactionNotExist, | |||
| Message: fmt.Sprintf("Could not found global transaction xid = %s",gs.Xid), | |||
| } | |||
| } | |||
| return gs,nil | |||
| } | |||
| func (core *DefaultCore) BranchReport(branchType meta.BranchType, | |||
| xid string, | |||
| branchId int64, | |||
| status meta.BranchStatus, | |||
| applicationData []byte) error { | |||
| gs,err := assertGlobalSessionNotNull(xid,true) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| bs := gs.GetBranch(branchId) | |||
| if bs == nil { | |||
| return &meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeBranchTransactionNotExist, | |||
| Message: fmt.Sprintf("Could not found branch session xid = %s branchId = %d", | |||
| xid,branchId), | |||
| } | |||
| } | |||
| bs.Status = status | |||
| holder.GetSessionHolder().RootSessionManager.UpdateBranchSessionStatus(bs,status) | |||
| logging.Logger.Infof("Successfully branch report xid = %s, branchId = %d",xid,bs.BranchId) | |||
| return nil | |||
| } | |||
| func (core *DefaultCore) LockQuery(branchType meta.BranchType, resourceId string, xid string, lockKeys string) (bool, error) { | |||
| return true,nil | |||
| } | |||
| func (core *DefaultCore) branchCommit(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) { | |||
| request := protocal.BranchCommitRequest{} | |||
| request.Xid = branchSession.Xid | |||
| request.BranchId = branchSession.BranchId | |||
| request.ResourceId = branchSession.ResourceId | |||
| request.ApplicationData = branchSession.ApplicationData | |||
| request.BranchType = branchSession.BranchType | |||
| resp, err := core.branchCommitSend(request,globalSession,branchSession) | |||
| if err != nil { | |||
| return 0,&meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeBranchTransactionNotExist, | |||
| Message: fmt.Sprintf("Send branch commit failed, xid = %s branchId = %d", | |||
| branchSession.Xid,branchSession.BranchId), | |||
| } | |||
| } | |||
| return resp,err | |||
| } | |||
| func (core *DefaultCore) branchCommitSend(request protocal.BranchCommitRequest, | |||
| globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus,error) { | |||
| resp,err := core.MessageSender.SendSyncRequest(branchSession.ResourceId,branchSession.ClientId,request) | |||
| if err != nil { | |||
| return 0,err | |||
| } | |||
| response := resp.(protocal.BranchCommitResponse) | |||
| return response.BranchStatus, nil | |||
| } | |||
| func (core *DefaultCore) branchRollback(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) { | |||
| request := protocal.BranchRollbackRequest{} | |||
| request.Xid = branchSession.Xid | |||
| request.BranchId = branchSession.BranchId | |||
| request.ResourceId = branchSession.ResourceId | |||
| request.ApplicationData = branchSession.ApplicationData | |||
| request.BranchType = branchSession.BranchType | |||
| resp, err := core.branchRollbackSend(request,globalSession,branchSession) | |||
| if err != nil { | |||
| return 0,&meta.TransactionException{ | |||
| Code: meta.TransactionExceptionCodeBranchTransactionNotExist, | |||
| Message: fmt.Sprintf("Send branch rollback failed, xid = %s branchId = %d", | |||
| branchSession.Xid,branchSession.BranchId), | |||
| } | |||
| } | |||
| return resp,err | |||
| } | |||
| func (core *DefaultCore) branchRollbackSend(request protocal.BranchRollbackRequest, | |||
| globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus,error) { | |||
| resp,err := core.MessageSender.SendSyncRequest(branchSession.ResourceId,branchSession.ClientId,request) | |||
| if err != nil { | |||
| return 0,err | |||
| } | |||
| response := resp.(protocal.BranchRollbackResponse) | |||
| return response.BranchStatus, nil | |||
| } | |||
| func (core *DefaultCore) Commit(xid string) (meta.GlobalStatus, error) { | |||
| globalSession := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid) | |||
| if globalSession == nil { | |||
| return meta.GlobalStatusFinished, nil | |||
| } | |||
| shouldCommit := func (gs *session.GlobalSession) bool { | |||
| gs.Lock() | |||
| defer gs.Unlock() | |||
| if gs.Active { | |||
| gs.Active = false | |||
| } | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(gs) | |||
| if gs.Status == meta.GlobalStatusBegin { | |||
| changeGlobalSessionStatus(gs,meta.GlobalStatusCommitting) | |||
| return true | |||
| } | |||
| return false | |||
| }(globalSession) | |||
| if !shouldCommit { | |||
| return globalSession.Status,nil | |||
| } | |||
| if globalSession.CanBeCommittedAsync() { | |||
| asyncCommit(globalSession) | |||
| return meta.GlobalStatusCommitted, nil | |||
| } else { | |||
| _,err := core.doGlobalCommit(globalSession,false) | |||
| if err != nil { | |||
| return 0,err | |||
| } | |||
| } | |||
| return globalSession.Status,nil | |||
| } | |||
| func (core *DefaultCore) doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error) { | |||
| var ( | |||
| success = true | |||
| err error | |||
| ) | |||
| evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime,0,globalSession.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| if globalSession.IsSaga() { | |||
| success,err = core.SAGACore.doGlobalCommit(globalSession,retrying) | |||
| } else { | |||
| for _,bs := range globalSession.GetSortedBranches() { | |||
| if bs.Status == meta.BranchStatusPhaseoneFailed { | |||
| removeBranchSession(globalSession,bs) | |||
| continue | |||
| } | |||
| branchStatus,err1 := core.branchCommit(globalSession,bs) | |||
| if err1 != nil { | |||
| logging.Logger.Errorf("Exception committing branch %v", bs) | |||
| if !retrying { | |||
| queueToRetryCommit(globalSession) | |||
| } | |||
| return false,err1 | |||
| } | |||
| switch branchStatus { | |||
| case meta.BranchStatusPhasetwoCommitted: | |||
| removeBranchSession(globalSession,bs) | |||
| continue | |||
| case meta.BranchStatusPhasetwoCommitFailedUnretryable: | |||
| { | |||
| // 二阶段提交失败且不能 Retry,不能异步提交,则移除 GlobalSession,Why? | |||
| if globalSession.CanBeCommittedAsync() { | |||
| logging.Logger.Errorf("By [%s], failed to commit branch %v",bs.Status.String(),bs) | |||
| continue | |||
| } else { | |||
| endCommitFailed(globalSession) | |||
| logging.Logger.Errorf("Finally, failed to commit global[%d] since branch[%d] commit failed",globalSession.Xid,bs.BranchId) | |||
| return false,nil | |||
| } | |||
| } | |||
| default: | |||
| { | |||
| if !retrying { | |||
| queueToRetryCommit(globalSession) | |||
| return false,nil | |||
| } | |||
| if globalSession.CanBeCommittedAsync() { | |||
| logging.Logger.Errorf("By [%s], failed to commit branch %v",bs.Status.String(),bs) | |||
| continue | |||
| } else { | |||
| logging.Logger.Errorf("ResultCodeFailed to commit global[%d] since branch[%d] commit failed, will retry later.",globalSession.Xid,bs.BranchId) | |||
| return false,nil | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if globalSession.HasBranch() { | |||
| logging.Logger.Infof("Global[%d] committing is NOT done.", globalSession.Xid) | |||
| return false,nil | |||
| } | |||
| } | |||
| if success { | |||
| endCommitted(globalSession) | |||
| evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime, | |||
| int64(util.CurrentTimeMillis()),globalSession.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| logging.Logger.Infof("Global[%d] committing is successfully done.", globalSession.Xid) | |||
| } | |||
| return success,err | |||
| } | |||
| func (core *DefaultCore) Rollback(xid string) (meta.GlobalStatus, error) { | |||
| globalSession := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid) | |||
| if globalSession == nil { | |||
| return meta.GlobalStatusFinished, nil | |||
| } | |||
| shouldRollBack := func (gs *session.GlobalSession) bool { | |||
| gs.Lock() | |||
| defer gs.Unlock() | |||
| if gs.Active { | |||
| gs.Active = false // Highlight: Firstly, close the session, then no more branch can be registered. | |||
| } | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(gs) | |||
| if gs.Status == meta.GlobalStatusBegin { | |||
| changeGlobalSessionStatus(gs,meta.GlobalStatusRollbacking) | |||
| return true | |||
| } | |||
| return false | |||
| }(globalSession) | |||
| if !shouldRollBack { | |||
| return globalSession.Status,nil | |||
| } | |||
| core.doGlobalRollback(globalSession,false) | |||
| return globalSession.Status,nil | |||
| } | |||
| func (core *DefaultCore) doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error) { | |||
| var ( | |||
| success = true | |||
| err error | |||
| ) | |||
| evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime, 0,globalSession.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| if globalSession.IsSaga() { | |||
| success,err = core.SAGACore.doGlobalRollback(globalSession,retrying) | |||
| } else { | |||
| for _,bs := range globalSession.GetSortedBranches() { | |||
| if bs.Status == meta.BranchStatusPhaseoneFailed { | |||
| removeBranchSession(globalSession, bs) | |||
| continue | |||
| } | |||
| branchStatus,err1 := core.branchRollback(globalSession,bs) | |||
| if err1 != nil { | |||
| logging.Logger.Errorf("Exception rollbacking branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId) | |||
| if !retrying { | |||
| queueToRetryRollback(globalSession) | |||
| } | |||
| return false,err1 | |||
| } | |||
| switch branchStatus { | |||
| case meta.BranchStatusPhasetwoRollbacked: | |||
| removeBranchSession(globalSession,bs) | |||
| logging.Logger.Infof("Successfully rollback branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId) | |||
| continue | |||
| case meta.BranchStatusPhasetwoRollbackFailedUnretryable: | |||
| endRollBackFailed(globalSession) | |||
| logging.Logger.Infof("ResultCodeFailed to rollback branch and stop retry xid=%d branchId=%d",globalSession.Xid,bs.BranchId) | |||
| return false,nil | |||
| default: | |||
| logging.Logger.Infof("ResultCodeFailed to rollback branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId) | |||
| if !retrying { | |||
| queueToRetryRollback(globalSession) | |||
| } | |||
| return false,nil | |||
| } | |||
| } | |||
| // In db mode, there is a problem of inconsistent data in multiple copies, resulting in new branch | |||
| // transaction registration when rolling back. | |||
| // 1. New branch transaction and rollback branch transaction have no data association | |||
| // 2. New branch transaction has data association with rollback branch transaction | |||
| // The second query can solve the first problem, and if it is the second problem, it may cause a rollback | |||
| // failure due to data changes. | |||
| gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(globalSession.Xid) | |||
| if gs != nil && gs.HasBranch() { | |||
| logging.Logger.Infof("Global[%d] rollbacking is NOT done.", globalSession.Xid) | |||
| return false, nil | |||
| } | |||
| } | |||
| if success { | |||
| endRollbacked(globalSession) | |||
| evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime, | |||
| int64(util.CurrentTimeMillis()),globalSession.Status) | |||
| event.EventBus.GlobalTransactionEventChannel <- evt | |||
| logging.Logger.Infof("Successfully rollback global, xid = %d", globalSession.Xid) | |||
| } | |||
| return success,err | |||
| } | |||
| func (core *DefaultCore) GetStatus(xid string) (meta.GlobalStatus, error) { | |||
| gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid) | |||
| if gs == nil { | |||
| return meta.GlobalStatusFinished,nil | |||
| } else { | |||
| return gs.Status,nil | |||
| } | |||
| } | |||
| func (core *DefaultCore) GlobalReport(xid string, globalStatus meta.GlobalStatus) (meta.GlobalStatus, error) { | |||
| gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid) | |||
| if gs == nil { | |||
| return globalStatus,nil | |||
| } | |||
| core.doGlobalReport(gs,xid,globalStatus) | |||
| return gs.Status,nil | |||
| } | |||
| func (core *DefaultCore) doGlobalReport(globalSession *session.GlobalSession,xid string,globalStatus meta.GlobalStatus) error { | |||
| if globalSession.IsSaga() { | |||
| return core.SAGACore.doGlobalReport(globalSession,xid,globalStatus) | |||
| } | |||
| return nil | |||
| } | |||
| func endRollbacked(globalSession *session.GlobalSession) { | |||
| if isTimeoutGlobalStatus(globalSession.Status) { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbacked) | |||
| } else { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbacked) | |||
| } | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(globalSession) | |||
| holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession) | |||
| } | |||
| func endRollBackFailed(globalSession *session.GlobalSession) { | |||
| if isTimeoutGlobalStatus(globalSession.Status) { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbackFailed) | |||
| } else { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbackFailed) | |||
| } | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(globalSession) | |||
| holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession) | |||
| } | |||
| func queueToRetryRollback(globalSession *session.GlobalSession) { | |||
| holder.GetSessionHolder().RetryRollbackingSessionManager.AddGlobalSession(globalSession) | |||
| if isTimeoutGlobalStatus(globalSession.Status) { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbackRetrying) | |||
| } else { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbackRetrying) | |||
| } | |||
| } | |||
| func isTimeoutGlobalStatus(status meta.GlobalStatus) bool { | |||
| return status == meta.GlobalStatusTimeoutRollbacked || | |||
| status == meta.GlobalStatusTimeoutRollbackFailed || | |||
| status == meta.GlobalStatusTimeoutRollbacking || | |||
| status == meta.GlobalStatusTimeoutRollbackRetrying | |||
| } | |||
| func endCommitted(globalSession *session.GlobalSession) { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitted) | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(globalSession) | |||
| holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession) | |||
| } | |||
| func queueToRetryCommit(globalSession *session.GlobalSession) { | |||
| holder.GetSessionHolder().RetryCommittingSessionManager.AddGlobalSession(globalSession) | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitRetrying) | |||
| } | |||
| func endCommitFailed(globalSession *session.GlobalSession) { | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitFailed) | |||
| lock.GetLockManager().ReleaseGlobalSessionLock(globalSession) | |||
| holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession) | |||
| } | |||
| func asyncCommit(globalSession *session.GlobalSession) { | |||
| holder.GetSessionHolder().AsyncCommittingSessionManager.AddGlobalSession(globalSession) | |||
| changeGlobalSessionStatus(globalSession,meta.GlobalStatusAsyncCommitting) | |||
| } | |||
| func changeGlobalSessionStatus(globalSession *session.GlobalSession, status meta.GlobalStatus) { | |||
| globalSession.Status = status | |||
| holder.GetSessionHolder().RootSessionManager.UpdateGlobalSessionStatus(globalSession,meta.GlobalStatusAsyncCommitting) | |||
| } | |||
| func removeBranchSession(globalSession *session.GlobalSession,branchSession *session.BranchSession) { | |||
| lock.GetLockManager().ReleaseLock(branchSession) | |||
| globalSession.Remove(branchSession) | |||
| holder.GetSessionHolder().RootSessionManager.RemoveBranchSession(globalSession,branchSession) | |||
| } | |||
| @@ -0,0 +1,372 @@ | |||
| package server | |||
| import ( | |||
| "github.com/pkg/errors" | |||
| "github.com/dubbogo/getty" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/model" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| ) | |||
| var ( | |||
| /** | |||
| * resourceId -> applicationId -> ip -> port -> RpcContext | |||
| */ | |||
| rm_sessions = sync.Map{} | |||
| /** | |||
| * ip+appname -> port -> RpcContext | |||
| */ | |||
| tm_sessions = sync.Map{} | |||
| ) | |||
| const ( | |||
| ClientIdSplitChar = ":" | |||
| DbkeysSplitChar = "," | |||
| ) | |||
| type GettySessionManager struct { | |||
| IdentifiedSessions *sync.Map | |||
| } | |||
| var SessionManager GettySessionManager | |||
| func init() { | |||
| SessionManager = GettySessionManager{IdentifiedSessions:&sync.Map{}} | |||
| } | |||
| func (manager *GettySessionManager) IsRegistered(session getty.Session) bool { | |||
| _,ok := manager.IdentifiedSessions.Load(session) | |||
| return ok | |||
| } | |||
| func (manager *GettySessionManager) GetRoleFromGettySession(session getty.Session) meta.TransactionRole { | |||
| context, ok := manager.IdentifiedSessions.Load(session) | |||
| if ok { | |||
| return context.(*RpcContext).ClientRole | |||
| } | |||
| return 0 | |||
| } | |||
| func (manager *GettySessionManager) GetContextFromIdentified(session getty.Session) *RpcContext { | |||
| context, ok := manager.IdentifiedSessions.Load(session) | |||
| if ok { | |||
| rpcContext := context.(*RpcContext) | |||
| return rpcContext | |||
| } | |||
| return nil | |||
| } | |||
| func (manager *GettySessionManager) RegisterTmGettySession(request protocal.RegisterTMRequest,session getty.Session) { | |||
| //todo check version | |||
| rpcContext := buildGettySessionHolder(meta.TMROLE,request.Version,request.ApplicationId,request.TransactionServiceGroup,"",session) | |||
| rpcContext.HoldInIdentifiedGettySessions(manager.IdentifiedSessions) | |||
| clientIdentified := rpcContext.ApplicationId + ClientIdSplitChar + getClientIpFromGettySession(session) | |||
| clientIdentifiedMap,_ := tm_sessions.LoadOrStore(clientIdentified,&sync.Map{}) | |||
| cMap := clientIdentifiedMap.(*sync.Map) | |||
| rpcContext.HoldInClientGettySessions(cMap) | |||
| } | |||
| func (manager *GettySessionManager) RegisterRmGettySession(resourceManagerRequest protocal.RegisterRMRequest,session getty.Session){ | |||
| //todo check version | |||
| var rpcContext *RpcContext | |||
| dbKeySet := dbKeyToSet(resourceManagerRequest.ResourceIds) | |||
| context,ok := manager.IdentifiedSessions.Load(session) | |||
| if ok { | |||
| rpcContext = context.(*RpcContext) | |||
| rpcContext.AddResources(dbKeySet) | |||
| } else { | |||
| rpcContext = buildGettySessionHolder(meta.RMROLE,resourceManagerRequest.Version,resourceManagerRequest.ApplicationId, | |||
| resourceManagerRequest.TransactionServiceGroup,resourceManagerRequest.ResourceIds,session) | |||
| rpcContext.HoldInIdentifiedGettySessions(manager.IdentifiedSessions) | |||
| } | |||
| if dbKeySet == nil || dbKeySet.IsEmpty() { return } | |||
| for _,resourceId := range dbKeySet.List() { | |||
| applicationMap,_ := rm_sessions.LoadOrStore(resourceId,&sync.Map{}) | |||
| aMap,_ := applicationMap.(*sync.Map) | |||
| ipMap,_ := aMap.LoadOrStore(resourceManagerRequest.ApplicationId,&sync.Map{}) | |||
| iMap,_ := ipMap.(*sync.Map) | |||
| clientIp := getClientIpFromGettySession(session) | |||
| portMap,_ := iMap.LoadOrStore(clientIp,&sync.Map{}) | |||
| pMap,_ := portMap.(*sync.Map) | |||
| rpcContext.HoldInResourceManagerGettySessions(resourceId,pMap) | |||
| // 老实讲,我不知道为什么要写这么一个方法,双重保证? | |||
| manager.updateGettySessionsResource(resourceId,clientIp,resourceManagerRequest.ApplicationId) | |||
| } | |||
| } | |||
| func (manager *GettySessionManager) updateGettySessionsResource(resourceId string,clientIp string,applicationId string) { | |||
| applicationMap,_ := rm_sessions.Load(resourceId) | |||
| aMap,_ := applicationMap.(*sync.Map) | |||
| ipMap,_ := aMap.Load(applicationId) | |||
| iMap,_ := ipMap.(*sync.Map) | |||
| portMap,_ := iMap.Load(clientIp) | |||
| pMap,_ := portMap.(*sync.Map) | |||
| rm_sessions.Range(func (key interface{},value interface{}) bool { | |||
| resourceKey,ok := key.(string) | |||
| if ok && resourceKey != resourceId { | |||
| appMap,_ := value.(*sync.Map) | |||
| clientIpMap,clientIpMapLoaded := appMap.Load(applicationId) | |||
| if clientIpMapLoaded { | |||
| cipMap,_ := clientIpMap.(*sync.Map) | |||
| clientPortMap, clientPortMapLoaded := cipMap.Load(clientIp) | |||
| if clientPortMapLoaded { | |||
| cpMap := clientPortMap.(*sync.Map) | |||
| cpMap.Range(func (key interface{},value interface{}) bool{ | |||
| port,_ := key.(int) | |||
| rpcContext,_ := value.(*RpcContext) | |||
| _, ok := pMap.LoadOrStore(port,rpcContext) | |||
| if ok { | |||
| rpcContext.HoldInResourceManagerGettySessionsWithoutPortMap(resourceId,port) | |||
| } | |||
| return true | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| return true | |||
| }) | |||
| } | |||
| func (manager *GettySessionManager) GetSameClientGettySession(session getty.Session) getty.Session { | |||
| if !session.IsClosed() { | |||
| return session | |||
| } | |||
| rpcContext := manager.GetContextFromIdentified(session) | |||
| if rpcContext == nil { | |||
| logging.Logger.Errorf("rpcContext is null,channel:{%v},active:{%t}",session,!session.IsClosed()) | |||
| } | |||
| if !rpcContext.session.IsClosed() { | |||
| return rpcContext.session | |||
| } | |||
| clientPort := getClientPortFromGettySession(session) | |||
| if rpcContext.ClientRole == meta.TMROLE { | |||
| clientIdentified := rpcContext.ApplicationId + ClientIdSplitChar + getClientIpFromGettySession(session) | |||
| clientRpcMap, ok := tm_sessions.Load(clientIdentified) | |||
| if !ok { | |||
| return nil | |||
| } | |||
| clientMap := clientRpcMap.(*sync.Map) | |||
| return getGettySessionFromSameClientMap(clientMap,clientPort) | |||
| } else if rpcContext.ClientRole == meta.RMROLE { | |||
| var sameClientSession getty.Session | |||
| rpcContext.ClientRMHolderMap.Range(func (key interface{},value interface{}) bool { | |||
| clientRmMap := value.(*sync.Map) | |||
| sameClientSession = getGettySessionFromSameClientMap(clientRmMap,clientPort) | |||
| if sameClientSession != nil { | |||
| return false | |||
| } | |||
| return true | |||
| }) | |||
| return sameClientSession | |||
| } | |||
| return nil | |||
| } | |||
| func getGettySessionFromSameClientMap(clientGettySessionMap *sync.Map,exclusivePort int) getty.Session { | |||
| var session getty.Session | |||
| if clientGettySessionMap != nil { | |||
| clientGettySessionMap.Range(func (key interface{},value interface{}) bool { | |||
| port,ok := key.(int) | |||
| if ok { | |||
| if port == exclusivePort { | |||
| clientGettySessionMap.Delete(key) | |||
| return true | |||
| } | |||
| } | |||
| context := value.(*RpcContext) | |||
| session = context.session | |||
| if !session.IsClosed() { | |||
| return false | |||
| } | |||
| clientGettySessionMap.Delete(key) | |||
| return true | |||
| }) | |||
| } | |||
| return session | |||
| } | |||
| func (manager *GettySessionManager) GetGettySession(resourceId string,clientId string) (getty.Session,error) { | |||
| var resultSession getty.Session | |||
| clientIdInfo := strings.Split(clientId,ClientIdSplitChar) | |||
| if clientIdInfo == nil || len(clientIdInfo) != 3 { | |||
| return nil,errors.Errorf("Invalid Client ID:%d",clientId) | |||
| } | |||
| targetApplicationId := clientIdInfo[0] | |||
| targetIP := clientIdInfo[1] | |||
| targetPort,_ := strconv.Atoi(clientIdInfo[2]) | |||
| applicationMap,ok := rm_sessions.Load(resourceId) | |||
| if targetApplicationId == "" || !ok || applicationMap == nil { | |||
| logging.Logger.Infof("No channel is available for resource[%s]",resourceId) | |||
| } | |||
| appMap,_ := applicationMap.(*sync.Map) | |||
| clientIpMap,clientIpMapLoaded := appMap.Load(targetApplicationId) | |||
| if clientIpMapLoaded { | |||
| ipMap,_ := clientIpMap.(*sync.Map) | |||
| portMap,portMapLoaded := ipMap.Load(targetIP) | |||
| if portMapLoaded { | |||
| pMap,_ := portMap.(*sync.Map) | |||
| context,contextLoaded := pMap.Load(targetPort) | |||
| // Firstly, try to find the original channel through which the branch was registered. | |||
| if contextLoaded { | |||
| rpcContext := context.(*RpcContext) | |||
| if !rpcContext.session.IsClosed() { | |||
| resultSession = rpcContext.session | |||
| logging.Logger.Debugf("Just got exactly the one %v for %s",rpcContext.session,clientId) | |||
| } else { | |||
| pMap.Delete(targetPort) | |||
| logging.Logger.Infof("Removed inactive %d",rpcContext.session) | |||
| } | |||
| } | |||
| // The original channel was broken, try another one. | |||
| if resultSession == nil { | |||
| pMap.Range(func (key interface{},value interface{}) bool { | |||
| rpcContext := value.(*RpcContext) | |||
| if !rpcContext.session.IsClosed() { | |||
| resultSession = rpcContext.session | |||
| logging.Logger.Infof("Choose %v on the same IP[%s] as alternative of %s",rpcContext.session,targetIP,clientId) | |||
| //跳出 range 循环 | |||
| return false | |||
| } else { | |||
| pMap.Delete(key) | |||
| logging.Logger.Infof("Removed inactive %d",rpcContext.session) | |||
| } | |||
| return true | |||
| }) | |||
| } | |||
| } | |||
| // No channel on the this app node, try another one. | |||
| if resultSession == nil { | |||
| ipMap.Range(func (key interface{},value interface{}) bool { | |||
| ip := key.(string) | |||
| if ip == targetIP { return true } | |||
| portMapOnOtherIP,_ := value.(*sync.Map) | |||
| if portMapOnOtherIP == nil { return true } | |||
| portMapOnOtherIP.Range(func (key interface{},value interface {}) bool { | |||
| rpcContext := value.(*RpcContext) | |||
| if !rpcContext.session.IsClosed() { | |||
| resultSession = rpcContext.session | |||
| logging.Logger.Infof("Choose %v on the same application[%s] as alternative of %s",rpcContext.session,targetApplicationId,clientId) | |||
| //跳出 range 循环 | |||
| return false | |||
| } else { | |||
| portMapOnOtherIP.Delete(key) | |||
| logging.Logger.Infof("Removed inactive %d",rpcContext.session) | |||
| } | |||
| return true | |||
| }) | |||
| if resultSession != nil { return false } | |||
| return true | |||
| }) | |||
| } | |||
| } | |||
| if resultSession == nil { | |||
| resultSession = tryOtherApp(appMap,targetApplicationId) | |||
| if resultSession == nil { | |||
| logging.Logger.Infof("No channel is available for resource[%s] as alternative of %s",resourceId,clientId) | |||
| } else { | |||
| logging.Logger.Infof("Choose %v on the same resource[%s] as alternative of %s", resultSession, resourceId, clientId) | |||
| } | |||
| } | |||
| return resultSession,nil | |||
| } | |||
| func tryOtherApp(applicationMap *sync.Map,myApplicationId string) getty.Session { | |||
| var chosenChannel getty.Session | |||
| applicationMap.Range(func (key interface{},value interface{}) bool { | |||
| applicationId := key.(string) | |||
| if myApplicationId != "" && applicationId == myApplicationId {return true} | |||
| targetIPMap,_ := value.(*sync.Map) | |||
| targetIPMap.Range(func (key interface{},value interface{}) bool { | |||
| if value == nil { return true } | |||
| portMap,_ := value.(*sync.Map) | |||
| portMap.Range(func (key interface{},value interface{}) bool { | |||
| rpcContext := value.(*RpcContext) | |||
| if !rpcContext.session.IsClosed() { | |||
| chosenChannel = rpcContext.session | |||
| return false | |||
| } else { | |||
| portMap.Delete(key) | |||
| logging.Logger.Infof("Removed inactive %d",rpcContext.session) | |||
| } | |||
| return true | |||
| }) | |||
| if chosenChannel != nil { return false } | |||
| return true | |||
| }) | |||
| if chosenChannel != nil { return false } | |||
| return true | |||
| }) | |||
| return chosenChannel | |||
| } | |||
| func buildGettySessionHolder(role meta.TransactionRole,version string,applicationId string, | |||
| txServiceGroup string,dbKeys string,session getty.Session) *RpcContext { | |||
| return &RpcContext{ | |||
| ClientRole: role, | |||
| Version: version, | |||
| ApplicationId: applicationId, | |||
| TransactionServiceGroup: txServiceGroup, | |||
| ClientId: buildClientId(applicationId,session), | |||
| session: session, | |||
| ResourceSets: dbKeyToSet(dbKeys), | |||
| } | |||
| } | |||
| func dbKeyToSet(dbKey string) *model.Set { | |||
| if dbKey == "" { | |||
| return nil | |||
| } | |||
| keys := strings.Split(dbKey,DbkeysSplitChar) | |||
| set := model.NewSet() | |||
| for _,key := range keys { | |||
| set.Add(key) | |||
| } | |||
| return set | |||
| } | |||
| func buildClientId(applicationId string, session getty.Session) string { | |||
| return applicationId + ClientIdSplitChar + session.RemoteAddr() | |||
| } | |||
| func (manager *GettySessionManager) GetRmSessions() map[string]getty.Session { | |||
| sessions := make(map[string]getty.Session) | |||
| rm_sessions.Range(func (key interface{},value interface{}) bool { | |||
| resourceId,_ := key.(string) | |||
| applicationMap := value.(*sync.Map) | |||
| session := tryOtherApp(applicationMap,"") | |||
| if session == nil { | |||
| return false | |||
| } | |||
| sessions[resourceId] = session | |||
| return true | |||
| }) | |||
| return sessions | |||
| } | |||
| @@ -0,0 +1,190 @@ | |||
| package server | |||
| import ( | |||
| "bytes" | |||
| "github.com/dubbogo/getty" | |||
| "github.com/pkg/errors" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "github.com/dk-lockdown/seata-golang/protocal/codec" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| /** | |||
| * <pre> | |||
| * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |||
| * +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+ | |||
| * | magic |Proto| Full length | Head | Msg |Seria|Compr| RequestId | | |||
| * | code |colVer| (head+body) | Length |Type |lizer|ess | | | |||
| * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ | |||
| * | | | |||
| * | Head Map [Optional] | | |||
| * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ | |||
| * | | | |||
| * | body | | |||
| * | | | |||
| * | ... ... | | |||
| * +-----------------------------------------------------------------------------------------------+ | |||
| * </pre> | |||
| * <p> | |||
| * <li>Full Length: include all data </li> | |||
| * <li>Head Length: include head data from magic code to head map. </li> | |||
| * <li>Body Length: Full Length - Head Length</li> | |||
| * </p> | |||
| * https://github.com/seata/seata/issues/893 | |||
| */ | |||
| var ( | |||
| RpcServerPkgHandler = &RpcServerPackageHandler{} | |||
| ) | |||
| type RpcServerPackageHandler struct{} | |||
| func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(data)} | |||
| b0,_ := r.ReadByte() | |||
| b1,_ := r.ReadByte() | |||
| if b0 != protocal.MAGIC_CODE_BYTES[0] || b1 != protocal.MAGIC_CODE_BYTES[1] { | |||
| return nil,0,errors.Errorf("Unknown magic code: %b,%b",b0,b1) | |||
| } | |||
| r.ReadByte() | |||
| // TODO check version compatible here | |||
| fullLength,_,_ := r.ReadInt32() | |||
| headLength,_,_ := r.ReadInt16() | |||
| messageType,_ := r.ReadByte() | |||
| codecType,_ := r.ReadByte() | |||
| compressorType,_ := r.ReadByte() | |||
| requestId,_,_ := r.ReadInt32() | |||
| rpcMessage := protocal.RpcMessage{ | |||
| Codec:codecType, | |||
| Id:requestId, | |||
| Compressor:compressorType, | |||
| MessageType:messageType, | |||
| } | |||
| headMapLength := headLength - protocal.V1_HEAD_LENGTH | |||
| if headMapLength > 0 { | |||
| rpcMessage.HeadMap = headMapDecode(data[protocal.V1_HEAD_LENGTH+1:headMapLength]) | |||
| } | |||
| if messageType == protocal.MSGTYPE_HEARTBEAT_REQUEST { | |||
| rpcMessage.Body = protocal.HeartBeatMessagePing | |||
| } else if messageType == protocal.MSGTYPE_HEARTBEAT_RESPONSE { | |||
| rpcMessage.Body = protocal.HeartBeatMessagePong | |||
| } else { | |||
| bodyLength := fullLength - int32(headLength) | |||
| if bodyLength > 0 { | |||
| //todo compress | |||
| msg,_ := codec.MessageDecoder(codecType,data[headLength:]) | |||
| rpcMessage.Body = msg | |||
| } | |||
| } | |||
| return rpcMessage, int(fullLength), nil | |||
| } | |||
| func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { | |||
| var result = make([]byte,0) | |||
| msg := pkg.(protocal.RpcMessage) | |||
| fullLength := protocal.V1_HEAD_LENGTH | |||
| headLength := protocal.V1_HEAD_LENGTH | |||
| var b bytes.Buffer | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| result = append(result, protocal.MAGIC_CODE_BYTES[:2]...) | |||
| result = append(result, protocal.VERSION) | |||
| w.WriteByte(msg.MessageType) | |||
| w.WriteByte(msg.Codec) | |||
| w.WriteByte(msg.Compressor) | |||
| w.WriteInt32(msg.Id) | |||
| if msg.HeadMap != nil && len(msg.HeadMap) > 0 { | |||
| headMapBytes,headMapLength := headMapEncode(msg.HeadMap) | |||
| headLength += headMapLength | |||
| fullLength += headMapLength | |||
| w.Write(headMapBytes) | |||
| } | |||
| if msg.MessageType != protocal.MSGTYPE_HEARTBEAT_REQUEST && | |||
| msg.MessageType != protocal.MSGTYPE_HEARTBEAT_RESPONSE { | |||
| bodyBytes := codec.MessageEncoder(msg.Codec,msg.Body) | |||
| fullLength += len(bodyBytes) | |||
| w.Write(bodyBytes) | |||
| } | |||
| fullLen := int32(fullLength) | |||
| headLen := int16(headLength) | |||
| result = append(result, []byte{ byte(fullLen>>26),byte(fullLen>>16),byte(fullLen>>8),byte(fullLen) }...) | |||
| result = append(result, []byte{ byte(headLen>>8),byte(headLen) }...) | |||
| result = append(result,b.Bytes()...) | |||
| return result, nil | |||
| } | |||
| func headMapDecode(data []byte) map[string]string { | |||
| mp := make(map[string]string) | |||
| size := len(data) | |||
| if size == 0 { | |||
| return mp | |||
| } | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(data)} | |||
| readLength := 0 | |||
| for { | |||
| if readLength >= size { break } | |||
| var key, value string | |||
| lengthK,_,_ := r.ReadUint16() | |||
| if lengthK < 0 { | |||
| break | |||
| } else if lengthK == 0 { | |||
| key = "" | |||
| } else { | |||
| key,_,_ = r.ReadString(int(lengthK)) | |||
| } | |||
| lengthV,_,_ := r.ReadUint16() | |||
| if lengthV < 0 { | |||
| break | |||
| } else if lengthV == 0 { | |||
| value = "" | |||
| } else { | |||
| value,_,_ = r.ReadString(int(lengthV)) | |||
| } | |||
| mp[key] = value | |||
| readLength += int(lengthK + lengthV) | |||
| } | |||
| return mp | |||
| } | |||
| func headMapEncode(data map[string]string) ([]byte,int) { | |||
| var b bytes.Buffer | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| for k,v := range data{ | |||
| if k == "" { | |||
| w.WriteUint16(0) | |||
| } else { | |||
| w.WriteUint16(uint16(len(k))) | |||
| w.WriteString(k) | |||
| } | |||
| if v == "" { | |||
| w.WriteUint16(0) | |||
| } else { | |||
| w.WriteUint16(uint16(len(v))) | |||
| w.WriteString(v) | |||
| } | |||
| } | |||
| return b.Bytes(),b.Len() | |||
| } | |||
| @@ -0,0 +1,136 @@ | |||
| package server | |||
| import ( | |||
| "errors" | |||
| "github.com/dubbogo/getty" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/model" | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| ) | |||
| const IpPortSplitChar = ":" | |||
| type RpcContext struct { | |||
| ClientRole meta.TransactionRole | |||
| Version string | |||
| ApplicationId string | |||
| TransactionServiceGroup string | |||
| ClientId string | |||
| session getty.Session | |||
| ResourceSets *model.Set | |||
| /** | |||
| * <getty.Session,*RpcContext> | |||
| */ | |||
| ClientIDHolderMap *sync.Map | |||
| /** | |||
| * <int,RpcContext> | |||
| */ | |||
| ClientTMHolderMap *sync.Map | |||
| /** | |||
| * resourceId -> int -> RpcContext> | |||
| */ | |||
| ClientRMHolderMap *sync.Map | |||
| } | |||
| func (context *RpcContext) Release() { | |||
| clientPort := getClientPortFromGettySession(context.session) | |||
| if context.ClientIDHolderMap != nil { | |||
| context.ClientIDHolderMap = nil | |||
| } | |||
| if context.ClientRole == meta.TMROLE && context.ClientTMHolderMap != nil { | |||
| context.ClientTMHolderMap.Delete(clientPort) | |||
| context.ClientTMHolderMap = nil | |||
| } | |||
| if context.ClientRole == meta.RMROLE && context.ClientRMHolderMap != nil { | |||
| context.ClientRMHolderMap.Range(func (key interface{}, value interface{}) bool { | |||
| m := value.(*sync.Map) | |||
| m.Delete(clientPort) | |||
| return true | |||
| }) | |||
| context.ClientRMHolderMap = nil | |||
| } | |||
| if context.ResourceSets != nil { | |||
| context.ResourceSets.Clear() | |||
| } | |||
| } | |||
| func (context *RpcContext) HoldInClientGettySessions(clientTMHolderMap *sync.Map) error { | |||
| if context.ClientTMHolderMap != nil { | |||
| return errors.New("illegal state") | |||
| } | |||
| context.ClientTMHolderMap = clientTMHolderMap | |||
| clientPort := getClientPortFromGettySession(context.session) | |||
| context.ClientTMHolderMap.Store(clientPort,context) | |||
| return nil | |||
| } | |||
| func (context *RpcContext) HoldInIdentifiedGettySessions(clientIDHolderMap *sync.Map) error { | |||
| if context.ClientIDHolderMap != nil { | |||
| return errors.New("illegal state") | |||
| } | |||
| context.ClientIDHolderMap = clientIDHolderMap | |||
| context.ClientIDHolderMap.Store(context.session,context) | |||
| return nil | |||
| } | |||
| func (context *RpcContext) HoldInResourceManagerGettySessions(resourceId string,portMap *sync.Map) { | |||
| if context.ClientRMHolderMap == nil { | |||
| context.ClientRMHolderMap = &sync.Map{} | |||
| } | |||
| clientPort := getClientPortFromGettySession(context.session) | |||
| portMap.Store(clientPort,context) | |||
| context.ClientRMHolderMap.Store(resourceId,portMap) | |||
| } | |||
| func (context *RpcContext) HoldInResourceManagerGettySessionsWithoutPortMap(resourceId string,clientPort int) { | |||
| if context.ClientRMHolderMap == nil { | |||
| context.ClientRMHolderMap = &sync.Map{} | |||
| } | |||
| portMap,_ := context.ClientRMHolderMap.LoadOrStore(resourceId,&sync.Map{}) | |||
| pm := portMap.(*sync.Map) | |||
| pm.Store(clientPort,context) | |||
| } | |||
| func (context *RpcContext) AddResource(resource string) { | |||
| if resource != "" { | |||
| if context.ResourceSets == nil { | |||
| context.ResourceSets = model.NewSet() | |||
| } | |||
| context.ResourceSets.Add(resource) | |||
| } | |||
| } | |||
| func (context *RpcContext) AddResources(resources *model.Set) { | |||
| if resources != nil { | |||
| if context.ResourceSets == nil { | |||
| context.ResourceSets = model.NewSet() | |||
| } | |||
| for _,resource := range resources.List() { | |||
| context.ResourceSets.Add(resource) | |||
| } | |||
| } | |||
| } | |||
| func getClientIpFromGettySession(session getty.Session) string { | |||
| clientIp := session.RemoteAddr() | |||
| if strings.Contains(clientIp,IpPortSplitChar) { | |||
| idx := strings.Index(clientIp,IpPortSplitChar) | |||
| clientIp = clientIp[:idx] | |||
| } | |||
| return clientIp | |||
| } | |||
| func getClientPortFromGettySession(session getty.Session) int { | |||
| address := session.RemoteAddr() | |||
| port := 0 | |||
| if strings.Contains(address,IpPortSplitChar) { | |||
| idx := strings.LastIndex(address,IpPortSplitChar) | |||
| port,_ = strconv.Atoi(address[idx+1:]) | |||
| } | |||
| return port | |||
| } | |||
| @@ -0,0 +1,121 @@ | |||
| package server | |||
| import ( | |||
| "fmt" | |||
| "net" | |||
| "os" | |||
| "os/signal" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "syscall" | |||
| "time" | |||
| ) | |||
| import ( | |||
| "github.com/dubbogo/getty" | |||
| "github.com/dubbogo/gost/sync" | |||
| ) | |||
| var ( | |||
| srvGrpool *gxsync.TaskPool | |||
| ) | |||
| func SetServerGrpool() { | |||
| srvConf := config.GetServerConfig() | |||
| if srvConf.GettyConfig.GrPoolSize > 1 { | |||
| srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GettyConfig.GrPoolSize), | |||
| gxsync.WithTaskPoolTaskQueueLength(srvConf.GettyConfig.QueueLen), | |||
| gxsync.WithTaskPoolTaskQueueNumber(srvConf.GettyConfig.QueueNumber)) | |||
| } | |||
| } | |||
| type Server struct { | |||
| conf config.ServerConfig | |||
| tcpServer getty.Server | |||
| rpcHandler *DefaultCoordinator | |||
| } | |||
| func NewServer() *Server { | |||
| s := &Server{ | |||
| conf: config.GetServerConfig(), | |||
| } | |||
| coordinator := NewDefaultCoordinator(s.conf) | |||
| s.rpcHandler = coordinator | |||
| return s | |||
| } | |||
| func (s *Server) newSession(session getty.Session) error { | |||
| var ( | |||
| ok bool | |||
| tcpConn *net.TCPConn | |||
| ) | |||
| conf := s.conf | |||
| if conf.GettyConfig.GettySessionParam.CompressEncoding { | |||
| session.SetCompressType(getty.CompressZip) | |||
| } | |||
| if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { | |||
| panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) | |||
| } | |||
| tcpConn.SetNoDelay(conf.GettyConfig.GettySessionParam.TcpNoDelay) | |||
| tcpConn.SetKeepAlive(conf.GettyConfig.GettySessionParam.TcpKeepAlive) | |||
| if conf.GettyConfig.GettySessionParam.TcpKeepAlive { | |||
| tcpConn.SetKeepAlivePeriod(conf.GettyConfig.GettySessionParam.KeepAlivePeriod) | |||
| } | |||
| tcpConn.SetReadBuffer(conf.GettyConfig.GettySessionParam.TcpRBufSize) | |||
| tcpConn.SetWriteBuffer(conf.GettyConfig.GettySessionParam.TcpWBufSize) | |||
| session.SetName(conf.GettyConfig.GettySessionParam.SessionName) | |||
| session.SetMaxMsgLen(conf.GettyConfig.GettySessionParam.MaxMsgLen) | |||
| session.SetPkgHandler(RpcServerPkgHandler) | |||
| session.SetEventListener(s.rpcHandler) | |||
| session.SetWQLen(conf.GettyConfig.GettySessionParam.PkgWQSize) | |||
| session.SetReadTimeout(conf.GettyConfig.GettySessionParam.TcpReadTimeout) | |||
| session.SetWriteTimeout(conf.GettyConfig.GettySessionParam.TcpWriteTimeout) | |||
| session.SetCronPeriod((int)(conf.GettyConfig.SessionTimeout.Nanoseconds() / 1e6)) | |||
| session.SetWaitTime(conf.GettyConfig.GettySessionParam.WaitTimeout) | |||
| logging.Logger.Debugf("app accepts new session:%s\n", session.Stat()) | |||
| session.SetTaskPool(srvGrpool) | |||
| return nil | |||
| } | |||
| func (s *Server) Start(addr string) { | |||
| var ( | |||
| tcpServer getty.Server | |||
| ) | |||
| tcpServer = getty.NewTCPServer( | |||
| getty.WithLocalAddress(addr), | |||
| ) | |||
| tcpServer.RunEventLoop(s.newSession) | |||
| logging.Logger.Debugf("s bind addr{%s} ok!", addr) | |||
| s.tcpServer = tcpServer | |||
| c := make(chan os.Signal, 1) | |||
| signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) | |||
| for { | |||
| sig := <-c | |||
| logging.Logger.Info("get a signal %s", sig.String()) | |||
| switch sig { | |||
| case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT: | |||
| s.Stop() | |||
| time.Sleep(time.Second) | |||
| return | |||
| case syscall.SIGHUP: | |||
| default: | |||
| return | |||
| } | |||
| } | |||
| } | |||
| func (s *Server) Stop() { | |||
| s.tcpServer.Close() | |||
| s.rpcHandler.Stop() | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package server | |||
| import ( | |||
| "github.com/dubbogo/getty" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| ) | |||
| type IServerMessageListener interface { | |||
| OnTrxMessage(rpcMessage protocal.RpcMessage, session getty.Session) | |||
| OnRegRmMessage(request protocal.RpcMessage, session getty.Session) | |||
| OnRegTmMessage(request protocal.RpcMessage, session getty.Session) | |||
| OnCheckMessage(request protocal.RpcMessage, session getty.Session) | |||
| } | |||
| @@ -0,0 +1,78 @@ | |||
| package server | |||
| import ( | |||
| "github.com/dubbogo/getty" | |||
| "github.com/dk-lockdown/seata-golang/protocal" | |||
| "time" | |||
| ) | |||
| type IServerMessageSender interface { | |||
| /** | |||
| * Send response. | |||
| * | |||
| * @param request the request | |||
| * @param channel the channel | |||
| * @param msg the msg | |||
| */ | |||
| SendResponse(request protocal.RpcMessage, session getty.Session, msg interface{}) | |||
| /** | |||
| * Sync call to RM | |||
| * | |||
| * @param resourceId Resource ID | |||
| * @param clientId Client ID | |||
| * @param message Request message | |||
| * @return Response message | |||
| * @throws IOException . | |||
| * @throws TimeoutException the timeout exception | |||
| */ | |||
| SendSyncRequest(resourceId string, clientId string, message interface{}) (interface{},error) | |||
| /** | |||
| * Sync call to RM with timeout. | |||
| * | |||
| * @param resourceId Resource ID | |||
| * @param clientId Client ID | |||
| * @param message Request message | |||
| * @param timeout timeout of the call | |||
| * @return Response message | |||
| * @throws IOException . | |||
| * @throws TimeoutException the timeout exception | |||
| */ | |||
| SendSyncRequestWithTimeout(resourceId string, clientId string, message interface{}, timeout time.Duration) (interface{},error) | |||
| /** | |||
| * Send request with response object. | |||
| * send syn request for rm | |||
| * | |||
| * @param clientChannel the client channel | |||
| * @param message the message | |||
| * @return the object | |||
| * @throws TimeoutException the timeout exception | |||
| */ | |||
| SendSyncRequestByGettySession(session getty.Session, message interface{}) (interface{},error) | |||
| /** | |||
| * Send request with response object. | |||
| * send syn request for rm | |||
| * | |||
| * @param clientChannel the client channel | |||
| * @param message the message | |||
| * @param timeout the timeout | |||
| * @return the object | |||
| * @throws TimeoutException the timeout exception | |||
| */ | |||
| SendSyncRequestByGettySessionWithTimeout(session getty.Session, message interface{}, timeout time.Duration) (interface{},error) | |||
| /** | |||
| * ASync call to RM | |||
| * | |||
| * @param channel channel | |||
| * @param message Request message | |||
| * @return Response message | |||
| * @throws IOException . | |||
| * @throws TimeoutException the timeout exception | |||
| */ | |||
| SendASyncRequest(session getty.Session, message interface{}) error | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package server | |||
| import "github.com/dk-lockdown/seata-golang/protocal" | |||
| type TCInboundHandler interface { | |||
| doGlobalBegin(request protocal.GlobalBeginRequest,ctx RpcContext) protocal.GlobalBeginResponse | |||
| doGlobalStatus(request protocal.GlobalStatusRequest,ctx RpcContext) protocal.GlobalStatusResponse | |||
| doGlobalReport(request protocal.GlobalReportRequest,ctx RpcContext) protocal.GlobalReportResponse | |||
| doGlobalCommit(request protocal.GlobalCommitRequest,ctx RpcContext) protocal.GlobalCommitResponse | |||
| doGlobalRollback(request protocal.GlobalRollbackRequest,ctx RpcContext) protocal.GlobalRollbackResponse | |||
| doBranchRegister(request protocal.BranchRegisterRequest,ctx RpcContext) protocal.BranchRegisterResponse | |||
| doBranchReport(request protocal.BranchReportRequest,ctx RpcContext) protocal.BranchReportResponse | |||
| doLockCheck(request protocal.GlobalLockQueryRequest,ctx RpcContext) protocal.GlobalLockQueryResponse | |||
| } | |||
| @@ -0,0 +1,73 @@ | |||
| package server | |||
| import ( | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/rm" | |||
| "github.com/dk-lockdown/seata-golang/tc/session" | |||
| "github.com/dk-lockdown/seata-golang/tm" | |||
| ) | |||
| type ITransactionCoordinatorInbound interface { | |||
| tm.ITransactionManager | |||
| rm.IResourceManagerOutbound | |||
| } | |||
| type ITransactionCoordinatorOutbound interface { | |||
| /** | |||
| * Commit a branch transaction. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param branchSession the branch session | |||
| * @return Status of the branch after committing. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| branchCommit(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) | |||
| /** | |||
| * Rollback a branch transaction. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param branchSession the branch session | |||
| * @return Status of the branch after rollbacking. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| branchRollback(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) | |||
| } | |||
| type ITransactionCoordinator interface { | |||
| ITransactionCoordinatorInbound | |||
| ITransactionCoordinatorOutbound | |||
| /** | |||
| * Do global commit. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param retrying the retrying | |||
| * @return is global commit. | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error) | |||
| /** | |||
| * Do global rollback. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param retrying the retrying | |||
| * @return is global rollback. | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error) | |||
| /** | |||
| * Do global report. | |||
| * | |||
| * @param globalSession the global session | |||
| * @param xid Transaction id. | |||
| * @param param the global status | |||
| * @throws TransactionException the transaction exception | |||
| */ | |||
| doGlobalReport(globalSession *session.GlobalSession, xid string, param meta.GlobalStatus) error | |||
| } | |||
| @@ -0,0 +1,230 @@ | |||
| package session | |||
| import ( | |||
| "bytes" | |||
| "github.com/pkg/errors" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| type BranchSession struct{ | |||
| Xid string | |||
| TransactionId int64 | |||
| BranchId int64 | |||
| ResourceGroupId string | |||
| ResourceId string | |||
| LockKey string | |||
| BranchType meta.BranchType | |||
| Status meta.BranchStatus | |||
| ClientId string | |||
| ApplicationData []byte | |||
| } | |||
| func NewBranchSession() *BranchSession { | |||
| return &BranchSession{} | |||
| } | |||
| func NewBranchSessionByGlobal(gs GlobalSession, | |||
| branchType meta.BranchType, | |||
| resourceId string, | |||
| applicationData []byte, | |||
| lockKeys string, | |||
| clientId string) *BranchSession { | |||
| bs := NewBranchSession() | |||
| bs.SetXid(gs.Xid) | |||
| bs.SetTransactionId(gs.TransactionId) | |||
| bs.SetBranchId(util.GeneratorUUID()) | |||
| bs.SetBranchType(branchType) | |||
| bs.SetResourceId(resourceId) | |||
| bs.SetLockKey(lockKeys) | |||
| bs.SetClientId(clientId) | |||
| bs.SetApplicationData(applicationData) | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetXid(xid string) *BranchSession { | |||
| bs.Xid = xid | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetTransactionId(transactionId int64) *BranchSession { | |||
| bs.TransactionId = transactionId | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetBranchId(branchId int64) *BranchSession { | |||
| bs.BranchId = branchId | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetResourceGroupId(ResourceGroupId string) *BranchSession { | |||
| bs.ResourceGroupId = ResourceGroupId | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetResourceId(resourceId string) *BranchSession { | |||
| bs.ResourceId = resourceId | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetLockKey(lockKey string) *BranchSession { | |||
| bs.LockKey = lockKey | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetBranchType(branchType meta.BranchType) *BranchSession { | |||
| bs.BranchType = branchType | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetStatus(status meta.BranchStatus) *BranchSession { | |||
| bs.Status = status | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetClientId(clientId string) *BranchSession { | |||
| bs.ClientId = clientId | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) SetApplicationData(applicationData []byte) *BranchSession { | |||
| bs.ApplicationData = applicationData | |||
| return bs | |||
| } | |||
| func (bs *BranchSession) CompareTo(session *BranchSession) int { | |||
| return int(bs.BranchId - session.BranchId) | |||
| } | |||
| func (bs *BranchSession) Encode() ([]byte, error) { | |||
| var ( | |||
| zero32 int32 = 0 | |||
| zero16 int16 = 0 | |||
| ) | |||
| size := calBranchSessionSize(len(bs.ResourceId),len(bs.LockKey),len(bs.ClientId),len(bs.ApplicationData),len(bs.Xid)) | |||
| if size > config.GetStoreConfig().MaxBranchSessionSize { | |||
| if bs.LockKey == "" { | |||
| logging.Logger.Errorf("branch session size exceeded, size : %d maxBranchSessionSize : %d", size, config.GetStoreConfig().MaxBranchSessionSize) | |||
| //todo compress | |||
| return nil, errors.New("branch session size exceeded.") | |||
| } | |||
| } | |||
| var b bytes.Buffer | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| w.WriteInt64(bs.TransactionId) | |||
| w.WriteInt64(bs.BranchId) | |||
| if bs.ResourceId != "" { | |||
| w.WriteUint32(uint32(len(bs.ResourceId))) | |||
| w.WriteString( bs.ResourceId) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| if bs.LockKey != "" { | |||
| w.WriteUint32(uint32(len(bs.LockKey))) | |||
| w.WriteString( bs.LockKey) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| if bs.ClientId != "" { | |||
| w.WriteUint16(uint16(len(bs.ClientId))) | |||
| w.WriteString(bs.ClientId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if bs.ApplicationData != nil { | |||
| w.WriteUint32(uint32(len(bs.ApplicationData))) | |||
| w.Write(bs.ApplicationData) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| if bs.Xid != "" { | |||
| w.WriteUint32(uint32(len(bs.Xid))) | |||
| w.WriteString( bs.Xid) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| w.WriteByte(byte(bs.BranchType)) | |||
| w.WriteByte(byte(bs.Status)) | |||
| return b.Bytes(), nil | |||
| } | |||
| func (bs *BranchSession) Decode(b []byte) { | |||
| var length32 uint32 = 0 | |||
| var length16 uint16 = 0 | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(b)} | |||
| bs.TransactionId, _, _ = r.ReadInt64() | |||
| bs.BranchId, _, _ = r.ReadInt64() | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { bs.ResourceId, _, _ = r.ReadString(int(length32)) } | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { bs.LockKey, _, _ = r.ReadString(int(length32)) } | |||
| length16, _, _ = r.ReadUint16() | |||
| if length16 > 0 { bs.ClientId, _, _ = r.ReadString(int(length16)) } | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { | |||
| bs.ApplicationData = make([]byte,int(length32)) | |||
| r.Read(bs.ApplicationData) | |||
| } | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { bs.Xid, _, _ = r.ReadString(int(length32)) } | |||
| branchType, _ := r.ReadByte() | |||
| bs.BranchType = meta.BranchType(branchType) | |||
| status, _ := r.ReadByte() | |||
| bs.Status = meta.BranchStatus(status) | |||
| } | |||
| func calBranchSessionSize(resourceIdLen int, | |||
| lockKeyLen int, | |||
| clientIdLen int, | |||
| applicationDataLen int, | |||
| xidLen int) int{ | |||
| size := 8 + // transactionId | |||
| 8 + // branchId | |||
| 4 + // resourceIdBytes.length | |||
| 4 + // lockKeyBytes.length | |||
| 2 + // clientIdBytes.length | |||
| 4 + // applicationDataBytes.length | |||
| 4 + // xidBytes.size | |||
| 1 + // statusCode | |||
| resourceIdLen + | |||
| lockKeyLen + | |||
| clientIdLen + | |||
| applicationDataLen + | |||
| xidLen + | |||
| 1 | |||
| return size | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package session | |||
| import ( | |||
| "github.com/stretchr/testify/assert" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "testing" | |||
| ) | |||
| func TestBranchSession_Encode_Decode(t *testing.T) { | |||
| bs := branchSessionProvider() | |||
| result,_ := bs.Encode() | |||
| newBs := &BranchSession{} | |||
| newBs.Decode(result) | |||
| assert.Equal(t,bs.TransactionId,newBs.TransactionId) | |||
| assert.Equal(t,bs.BranchId,newBs.BranchId) | |||
| assert.Equal(t,bs.ResourceId,newBs.ResourceId) | |||
| assert.Equal(t,bs.LockKey,newBs.LockKey) | |||
| assert.Equal(t,bs.ClientId,newBs.ClientId) | |||
| assert.Equal(t,bs.ApplicationData,newBs.ApplicationData) | |||
| } | |||
| func branchSessionProvider() *BranchSession { | |||
| bs := NewBranchSession(). | |||
| SetTransactionId(util.GeneratorUUID()). | |||
| SetBranchId(1). | |||
| SetResourceGroupId("my_test_tx_group"). | |||
| SetResourceId("tb_1"). | |||
| SetLockKey("t_1"). | |||
| SetBranchType(meta.BranchTypeAT). | |||
| SetStatus(meta.BranchStatusUnknown). | |||
| SetClientId("c1"). | |||
| SetApplicationData([]byte("{\"data\":\"test\"}")) | |||
| return bs | |||
| } | |||
| @@ -0,0 +1,297 @@ | |||
| package session | |||
| import ( | |||
| "bytes" | |||
| "github.com/pkg/errors" | |||
| "github.com/dk-lockdown/seata-golang/common" | |||
| "github.com/dk-lockdown/seata-golang/logging" | |||
| "github.com/dk-lockdown/seata-golang/meta" | |||
| "github.com/dk-lockdown/seata-golang/tc/config" | |||
| "github.com/dk-lockdown/seata-golang/util" | |||
| "sort" | |||
| "sync" | |||
| "vimagination.zapto.org/byteio" | |||
| ) | |||
| type GlobalSession struct { | |||
| sync.Mutex | |||
| Xid string | |||
| TransactionId int64 | |||
| Status meta.GlobalStatus | |||
| ApplicationId string | |||
| TransactionServiceGroup string | |||
| TransactionName string | |||
| Timeout int32 | |||
| BeginTime int64 | |||
| ApplicationData []byte | |||
| Active bool | |||
| BranchSessions map[*BranchSession]bool | |||
| } | |||
| func NewGlobalSession() *GlobalSession { | |||
| gs := &GlobalSession{ | |||
| BranchSessions: make(map[*BranchSession]bool), | |||
| } | |||
| gs.TransactionId = util.GeneratorUUID() | |||
| gs.Xid = common.XID.GenerateXID(gs.TransactionId) | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetXid(xid string) *GlobalSession { | |||
| gs.Xid = xid | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetTransactionId(transactionId int64) *GlobalSession { | |||
| gs.TransactionId = transactionId | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetStatus(status meta.GlobalStatus) *GlobalSession { | |||
| gs.Status = status | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetApplicationId(applicationId string) *GlobalSession { | |||
| gs.ApplicationId = applicationId | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetTransactionServiceGroup(transactionServiceGroup string) *GlobalSession { | |||
| gs.TransactionServiceGroup = transactionServiceGroup | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetTransactionName(transactionName string) *GlobalSession { | |||
| gs.TransactionName = transactionName | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetTimeout(timeout int32) *GlobalSession { | |||
| gs.Timeout = timeout | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetBeginTime(beginTime int64) *GlobalSession { | |||
| gs.BeginTime = beginTime | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetApplicationData(applicationData []byte) *GlobalSession { | |||
| gs.ApplicationData = applicationData | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) SetActive(active bool) *GlobalSession { | |||
| gs.Active = active | |||
| return gs | |||
| } | |||
| func (gs *GlobalSession) Add(branchSession *BranchSession) { | |||
| branchSession.Status = meta.BranchStatusRegistered | |||
| gs.BranchSessions[branchSession] = true | |||
| } | |||
| func (gs *GlobalSession) Remove(branchSession *BranchSession) { | |||
| delete(gs.BranchSessions, branchSession) | |||
| } | |||
| func (gs *GlobalSession) CanBeCommittedAsync() bool { | |||
| for branchSession := range gs.BranchSessions { | |||
| if branchSession.BranchType == meta.BranchTypeTCC { | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| func (gs *GlobalSession) IsSaga() bool { | |||
| for branchSession := range gs.BranchSessions { | |||
| if branchSession.BranchType == meta.BranchTypeSAGA { | |||
| return true | |||
| } else { | |||
| return false | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| func (gs *GlobalSession) IsTimeout() bool { | |||
| return (util.CurrentTimeMillis() - uint64(gs.BeginTime)) > uint64(gs.Timeout) | |||
| } | |||
| func (gs *GlobalSession) IsRollbackingDead() bool { | |||
| return (util.CurrentTimeMillis() - uint64(gs.BeginTime)) > uint64(2 * 6000) | |||
| } | |||
| func (gs *GlobalSession) GetSortedBranches() []*BranchSession { | |||
| var branchSessions = make([]*BranchSession, 0) | |||
| for branchSession := range gs.BranchSessions { | |||
| branchSessions = append(branchSessions, branchSession) | |||
| } | |||
| return branchSessions | |||
| } | |||
| func (gs *GlobalSession) GetReverseSortedBranches() []*BranchSession { | |||
| var branchSessions = gs.GetSortedBranches() | |||
| sort.Reverse(BranchSessionSlice(branchSessions)) | |||
| return branchSessions | |||
| } | |||
| type BranchSessionSlice []*BranchSession | |||
| func (p BranchSessionSlice) Len() int { return len(p) } | |||
| func (p BranchSessionSlice) Less(i, j int) bool { return p[i].CompareTo(p[j]) > 0 } | |||
| func (p BranchSessionSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } | |||
| func (gs *GlobalSession) GetBranch(branchId int64) *BranchSession { | |||
| gs.Lock() | |||
| defer gs.Unlock() | |||
| for branchSession := range gs.BranchSessions { | |||
| if branchSession.BranchId == branchId { | |||
| return branchSession | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| func (gs *GlobalSession) HasBranch() bool { | |||
| return len(gs.BranchSessions) > 0 | |||
| } | |||
| func (gs *GlobalSession) Begin() { | |||
| gs.Status = meta.GlobalStatusBegin | |||
| gs.BeginTime = int64(util.CurrentTimeMillis()) | |||
| gs.Active = true | |||
| } | |||
| func (gs *GlobalSession) Encode() ([]byte, error) { | |||
| var ( | |||
| zero32 int32 = 0 | |||
| zero16 int16 = 0 | |||
| ) | |||
| size := calGlobalSessionSize(len(gs.ApplicationId),len(gs.TransactionServiceGroup),len(gs.TransactionName),len(gs.Xid),len(gs.ApplicationData)) | |||
| if size > config.GetStoreConfig().MaxGlobalSessionSize { | |||
| logging.Logger.Errorf("global session size exceeded, size : %d maxBranchSessionSize : %d", size, config.GetStoreConfig().MaxGlobalSessionSize) | |||
| //todo compress | |||
| return nil, errors.New("global session size exceeded.") | |||
| } | |||
| var b bytes.Buffer | |||
| w := byteio.BigEndianWriter{Writer: &b} | |||
| w.WriteInt64(gs.TransactionId) | |||
| w.WriteInt32(gs.Timeout) | |||
| // applicationId 长度不会超过 256 | |||
| if gs.ApplicationId != "" { | |||
| w.WriteUint16(uint16(len(gs.ApplicationId))) | |||
| w.WriteString(gs.ApplicationId) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if gs.TransactionServiceGroup != "" { | |||
| w.WriteUint16(uint16(len(gs.TransactionServiceGroup))) | |||
| w.WriteString(gs.TransactionServiceGroup) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if gs.TransactionName != "" { | |||
| w.WriteUint16(uint16(len(gs.TransactionName))) | |||
| w.WriteString(gs.TransactionName) | |||
| } else { | |||
| w.WriteInt16(zero16) | |||
| } | |||
| if gs.Xid != "" { | |||
| w.WriteUint32(uint32(len(gs.Xid))) | |||
| w.WriteString(gs.Xid) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| if gs.ApplicationData != nil { | |||
| w.WriteUint32(uint32(len(gs.ApplicationData))) | |||
| w.Write(gs.ApplicationData) | |||
| } else { | |||
| w.WriteInt32(zero32) | |||
| } | |||
| w.WriteInt64(gs.BeginTime) | |||
| w.WriteByte(byte(gs.Status)) | |||
| return b.Bytes(), nil | |||
| } | |||
| func (gs *GlobalSession) Decode(b []byte) { | |||
| var length32 uint32 = 0 | |||
| var length16 uint16 = 0 | |||
| r := byteio.BigEndianReader{Reader:bytes.NewReader(b)} | |||
| gs.TransactionId, _, _ = r.ReadInt64() | |||
| gs.Timeout, _, _ = r.ReadInt32() | |||
| length16, _, _ = r.ReadUint16() | |||
| if length16 > 0 { gs.ApplicationId, _, _ = r.ReadString(int(length16)) } | |||
| length16, _, _ = r.ReadUint16() | |||
| if length16 > 0 { gs.TransactionServiceGroup, _, _ = r.ReadString(int(length16)) } | |||
| length16, _, _ = r.ReadUint16() | |||
| if length16 > 0 { gs.TransactionName, _, _ = r.ReadString(int(length16)) } | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { gs.Xid, _, _ = r.ReadString(int(length32)) } | |||
| length32, _, _ = r.ReadUint32() | |||
| if length32 > 0 { | |||
| gs.ApplicationData = make([]byte,length32,length32) | |||
| r.Read(gs.ApplicationData) | |||
| } | |||
| gs.BeginTime, _, _ = r.ReadInt64() | |||
| status, _ := r.ReadByte() | |||
| gs.Status = meta.GlobalStatus(status) | |||
| } | |||
| func calGlobalSessionSize( applicationIdLen int, | |||
| serviceGroupLen int, | |||
| txNameLen int, | |||
| xidLen int, | |||
| applicationDataLen int, | |||
| ) int{ | |||
| size := 8 + // transactionId | |||
| 4 + // timeout | |||
| 2 + // byApplicationIdBytes.length | |||
| 2 + // byServiceGroupBytes.length | |||
| 2 + // byTxNameBytes.length | |||
| 4 + // xidBytes.length | |||
| 4 + // applicationDataBytes.length | |||
| 8 + // beginTime | |||
| 1 + // statusCode | |||
| applicationIdLen + | |||
| serviceGroupLen + | |||
| txNameLen + | |||
| xidLen + | |||
| applicationDataLen | |||
| return size | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| package session | |||
| import ( | |||
| "github.com/stretchr/testify/assert" | |||
| "testing" | |||
| ) | |||
| func TestGlobalSession_Encode_Decode(t *testing.T) { | |||
| gs := globalSessionProvider() | |||
| result, _ := gs.Encode() | |||
| newGs := &GlobalSession{} | |||
| newGs.Decode(result) | |||
| assert.Equal(t,newGs.TransactionId,gs.TransactionId) | |||
| assert.Equal(t,newGs.Timeout,gs.Timeout) | |||
| assert.Equal(t,newGs.ApplicationId,gs.ApplicationId) | |||
| assert.Equal(t,newGs.TransactionServiceGroup,gs.TransactionServiceGroup) | |||
| assert.Equal(t,newGs.TransactionName,gs.TransactionName) | |||
| } | |||
| func globalSessionProvider() *GlobalSession{ | |||
| gs := NewGlobalSession(). | |||
| SetApplicationId("demo-app"). | |||
| SetTransactionServiceGroup("my_test_tx_group"). | |||
| SetTransactionName("test"). | |||
| SetTimeout(6000). | |||
| SetActive(true) | |||
| return gs | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package session | |||
| type SessionStorable interface { | |||
| /** | |||
| * Encode byte [ ]. | |||
| * | |||
| * @return the byte [ ] | |||
| */ | |||
| Encode() ([]byte, error) | |||
| /** | |||
| * Decode. | |||
| * | |||
| * @param src the src | |||
| */ | |||
| Decode(src []byte) | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| package tm | |||
| import "github.com/dk-lockdown/seata-golang/meta" | |||
| type ITransactionManager interface { | |||
| /** | |||
| * GlobalStatus_Begin a new global transaction. | |||
| * | |||
| * @param applicationId ID of the application who begins this transaction. | |||
| * @param transactionServiceGroup ID of the transaction service group. | |||
| * @param name Give a name to the global transaction. | |||
| * @param timeout Timeout of the global transaction. | |||
| * @return XID of the global transaction | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| Begin(applicationId string, transactionServiceGroup string, name string, timeout int32) (string, error) | |||
| /** | |||
| * Global commit. | |||
| * | |||
| * @param xid XID of the global transaction. | |||
| * @return Status of the global transaction after committing. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| Commit(xid string) (meta.GlobalStatus, error) | |||
| /** | |||
| * Global rollback. | |||
| * | |||
| * @param xid XID of the global transaction | |||
| * @return Status of the global transaction after rollbacking. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| Rollback(xid string) (meta.GlobalStatus, error) | |||
| /** | |||
| * Get current status of the give transaction. | |||
| * | |||
| * @param xid XID of the global transaction. | |||
| * @return Current status of the global transaction. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| GetStatus(xid string) (meta.GlobalStatus, error) | |||
| /** | |||
| * Global report. | |||
| * | |||
| * @param xid XID of the global transaction. | |||
| * @param globalStatus Status of the global transaction. | |||
| * @return Status of the global transaction. | |||
| * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown | |||
| * out. | |||
| */ | |||
| GlobalReport(xid string, globalStatus meta.GlobalStatus) (meta.GlobalStatus, error) | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| package util | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "hash/crc32" | |||
| ) | |||
| // String hashes a string to a unique hashcode. | |||
| // | |||
| // crc32 returns a uint32, but for our use we need | |||
| // and non negative integer. Here we cast to an integer | |||
| // and invert it if the result is negative. | |||
| func String(s string) int { | |||
| v := int(crc32.ChecksumIEEE([]byte(s))) | |||
| if v >= 0 { | |||
| return v | |||
| } | |||
| if -v >= 0 { | |||
| return -v | |||
| } | |||
| // v == MinInt | |||
| return 0 | |||
| } | |||
| // Strings hashes a list of strings to a unique hashcode. | |||
| func Strings(strings []string) string { | |||
| var buf bytes.Buffer | |||
| for _, s := range strings { | |||
| buf.WriteString(fmt.Sprintf("%s-", s)) | |||
| } | |||
| return fmt.Sprintf("%d", String(buf.String())) | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| package util | |||
| import ( | |||
| "time" | |||
| ) | |||
| const ( | |||
| TimeFormat = "2006-01-02 15:04:05" | |||
| DateFormat = "2006-01-02" | |||
| UnixTimeUnitOffset = uint64(time.Millisecond / time.Nanosecond) | |||
| ) | |||
| // FormatTimeMillis converts Millisecond to time string | |||
| // tsMillis accurates to millisecond,otherwise, an exception will occur | |||
| func FormatTimeMillis(tsMillis uint64) string { | |||
| return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(TimeFormat) | |||
| } | |||
| // FormatDate converts to date string | |||
| // tsMillis accurates to millisecond,otherwise, an exception will occur | |||
| func FormatDate(tsMillis uint64) string { | |||
| return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(DateFormat) | |||
| } | |||
| // Returns the current Unix timestamp in milliseconds. | |||
| func CurrentTimeMillis() uint64 { | |||
| return uint64(time.Now().UnixNano()) / UnixTimeUnitOffset | |||
| } | |||
| // Returns the current Unix timestamp in nanoseconds. | |||
| func CurrentTimeNano() uint64 { | |||
| return uint64(time.Now().UnixNano()) | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| package util | |||
| import ( | |||
| "sync/atomic" | |||
| ) | |||
| var ( | |||
| UUID int64 = 1000 | |||
| serverNodeId = 1 | |||
| UUID_INTERNAL int64 = 2000000000 | |||
| initUUID int64 = 0 | |||
| ) | |||
| func GeneratorUUID() int64 { | |||
| id := atomic.AddInt64(&UUID,1) | |||
| if id >= getMaxUUID() { | |||
| if UUID >= id { | |||
| newId := id - UUID_INTERNAL | |||
| atomic.CompareAndSwapInt64(&UUID,id, newId) | |||
| return newId | |||
| } | |||
| } | |||
| return id | |||
| } | |||
| func SetUUID(expect int64, update int64) bool { | |||
| return atomic.CompareAndSwapInt64(&UUID,expect, update) | |||
| } | |||
| func getMaxUUID() int64 { | |||
| return UUID_INTERNAL * (int64(serverNodeId) +1) | |||
| } | |||
| func GetInitUUID() int64 { | |||
| return initUUID | |||
| } | |||
| func Init(svrNodeId int) { | |||
| // 2019-01-01 与 java 版 seata 一致 | |||
| var base uint64 = 1546272000000 | |||
| serverNodeId = svrNodeId | |||
| atomic.CompareAndSwapInt64(&UUID,UUID,UUID_INTERNAL*int64(serverNodeId)) | |||
| current := CurrentTimeMillis() | |||
| id := atomic.AddInt64(&UUID, int64((current - base)/UnixTimeUnitOffset)) | |||
| initUUID = id | |||
| } | |||