@@ -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 | |||
} |