From d7dfc72f11da2c8c746006d857d5fabca3a7dc1c Mon Sep 17 00:00:00 2001 From: liuxiaomin Date: Fri, 10 Apr 2020 17:34:17 +0800 Subject: [PATCH] 0.0.0 --- .gitignore | 19 + README.md | 13 + common/xid.go | 26 + docs/seata-golang.svg | 1 + go.mod | 17 + go.sum | 84 ++ logging/logging.go | 186 +++++ logging/logging_test.go | 7 + meta/branch_status.go | 102 +++ meta/branch_type.go | 36 + meta/global_status.go | 142 ++++ meta/transaction_exception_code.go | 110 +++ meta/transaction_role.go | 33 + model/resource.go | 28 + model/set.go | 69 ++ protocal/codec/codec.go | 243 ++++++ protocal/codec/seata_decoder.go | 773 +++++++++++++++++++ protocal/codec/seata_encoder.go | 560 ++++++++++++++ protocal/constant.go | 13 + protocal/heart_beat_message.go | 16 + protocal/identify.go | 26 + protocal/merged_message.go | 17 + protocal/message_type.go | 117 +++ protocal/message_type_aware.go | 5 + protocal/result_code.go | 18 + protocal/rm.go | 18 + protocal/rpc_message.go | 10 + protocal/tm.go | 17 + protocal/transaction.go | 227 ++++++ rm/resource_manager.go | 109 +++ tc/app/cmd/main.go | 20 + tc/app/profiles/dev/config.yml | 28 + tc/config/getty_config.go | 38 + tc/config/server_config.go | 104 +++ tc/config/store_config.go | 58 ++ tc/config/undo_config.go | 5 + tc/event/event_manager.go | 11 + tc/event/global_transaction_event.go | 41 + tc/holder/default_session_manager.go | 66 ++ tc/holder/default_session_manager_test.go | 87 +++ tc/holder/file_based_session_manager.go | 215 ++++++ tc/holder/file_based_session_manager_test.go | 132 ++++ tc/holder/file_transaction_store_manager.go | 232 ++++++ tc/holder/session_holder.go | 93 +++ tc/holder/session_manager.go | 181 +++++ tc/holder/transaction_store_manager.go | 135 ++++ tc/holder/transaction_write_store.go | 53 ++ tc/lock/lock_manager.go | 56 ++ tc/lock/lock_manager_test.go | 152 ++++ tc/lock/locker.go | 16 + tc/lock/memory_lock.go | 188 +++++ tc/lock/row_lock.go | 76 ++ tc/model/session_condition.go | 11 + tc/server/default_coordinator.go | 659 ++++++++++++++++ tc/server/default_core.go | 584 ++++++++++++++ tc/server/getty_session_manager.go | 372 +++++++++ tc/server/readwriter.go | 190 +++++ tc/server/rpc_context.go | 136 ++++ tc/server/server.go | 121 +++ tc/server/server_message_listener.go | 16 + tc/server/server_message_sender.go | 78 ++ tc/server/tc_inbound_handler.go | 14 + tc/server/transaction_coordinator.go | 73 ++ tc/session/branch_session.go | 230 ++++++ tc/session/branch_session_test.go | 37 + tc/session/global_session.go | 297 +++++++ tc/session/global_session_test.go | 31 + tc/session/session_storable.go | 17 + tm/transaction_manager.go | 59 ++ util/hashcode.go | 35 + util/time.go | 33 + util/uuid_generator.go | 46 ++ 72 files changed, 8068 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 common/xid.go create mode 100644 docs/seata-golang.svg create mode 100644 go.mod create mode 100644 go.sum create mode 100644 logging/logging.go create mode 100644 logging/logging_test.go create mode 100644 meta/branch_status.go create mode 100644 meta/branch_type.go create mode 100644 meta/global_status.go create mode 100644 meta/transaction_exception_code.go create mode 100644 meta/transaction_role.go create mode 100644 model/resource.go create mode 100644 model/set.go create mode 100644 protocal/codec/codec.go create mode 100644 protocal/codec/seata_decoder.go create mode 100644 protocal/codec/seata_encoder.go create mode 100644 protocal/constant.go create mode 100644 protocal/heart_beat_message.go create mode 100644 protocal/identify.go create mode 100644 protocal/merged_message.go create mode 100644 protocal/message_type.go create mode 100644 protocal/message_type_aware.go create mode 100644 protocal/result_code.go create mode 100644 protocal/rm.go create mode 100644 protocal/rpc_message.go create mode 100644 protocal/tm.go create mode 100644 protocal/transaction.go create mode 100644 rm/resource_manager.go create mode 100644 tc/app/cmd/main.go create mode 100644 tc/app/profiles/dev/config.yml create mode 100644 tc/config/getty_config.go create mode 100644 tc/config/server_config.go create mode 100644 tc/config/store_config.go create mode 100644 tc/config/undo_config.go create mode 100644 tc/event/event_manager.go create mode 100644 tc/event/global_transaction_event.go create mode 100644 tc/holder/default_session_manager.go create mode 100644 tc/holder/default_session_manager_test.go create mode 100644 tc/holder/file_based_session_manager.go create mode 100644 tc/holder/file_based_session_manager_test.go create mode 100644 tc/holder/file_transaction_store_manager.go create mode 100644 tc/holder/session_holder.go create mode 100644 tc/holder/session_manager.go create mode 100644 tc/holder/transaction_store_manager.go create mode 100644 tc/holder/transaction_write_store.go create mode 100644 tc/lock/lock_manager.go create mode 100644 tc/lock/lock_manager_test.go create mode 100644 tc/lock/locker.go create mode 100644 tc/lock/memory_lock.go create mode 100644 tc/lock/row_lock.go create mode 100644 tc/model/session_condition.go create mode 100644 tc/server/default_coordinator.go create mode 100644 tc/server/default_core.go create mode 100644 tc/server/getty_session_manager.go create mode 100644 tc/server/readwriter.go create mode 100644 tc/server/rpc_context.go create mode 100644 tc/server/server.go create mode 100644 tc/server/server_message_listener.go create mode 100644 tc/server/server_message_sender.go create mode 100644 tc/server/tc_inbound_handler.go create mode 100644 tc/server/transaction_coordinator.go create mode 100644 tc/session/branch_session.go create mode 100644 tc/session/branch_session_test.go create mode 100644 tc/session/global_session.go create mode 100644 tc/session/global_session_test.go create mode 100644 tc/session/session_storable.go create mode 100644 tm/transaction_manager.go create mode 100644 util/hashcode.go create mode 100644 util/time.go create mode 100644 util/uuid_generator.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7391b203 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..e46a022f --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/common/xid.go b/common/xid.go new file mode 100644 index 00000000..8603ca3a --- /dev/null +++ b/common/xid.go @@ -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 +} diff --git a/docs/seata-golang.svg b/docs/seata-golang.svg new file mode 100644 index 00000000..0e5b6624 --- /dev/null +++ b/docs/seata-golang.svg @@ -0,0 +1 @@ +seata-golangmeta枚举等modelresourcetcsessionbranch sessionglobal sessionconfigtc configlock configstore configconstructorbranch session lock channelglobal session lock channellockinterfacememory locketcd lockdb lockmodelsession conditionholderinterfacefile storedb storeetcd storesession managermemorydbrmconfigsql parserundo logtmlogginginterfaceconsole logfile logutil \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..cc1f07c3 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..ee4f0bca --- /dev/null +++ b/go.sum @@ -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= diff --git a/logging/logging.go b/logging/logging.go new file mode 100644 index 00000000..62ded835 --- /dev/null +++ b/logging/logging.go @@ -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...))) + } +} \ No newline at end of file diff --git a/logging/logging_test.go b/logging/logging_test.go new file mode 100644 index 00000000..3a42f3ce --- /dev/null +++ b/logging/logging_test.go @@ -0,0 +1,7 @@ +package logging + +import "testing" + +func TestSeataLogger_Info(t *testing.T) { + Logger.Info("there is a bug") +} \ No newline at end of file diff --git a/meta/branch_status.go b/meta/branch_status.go new file mode 100644 index 00000000..8bb019f1 --- /dev/null +++ b/meta/branch_status.go @@ -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) + } +} \ No newline at end of file diff --git a/meta/branch_type.go b/meta/branch_type.go new file mode 100644 index 00000000..ece65b9d --- /dev/null +++ b/meta/branch_type.go @@ -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) + } +} \ No newline at end of file diff --git a/meta/global_status.go b/meta/global_status.go new file mode 100644 index 00000000..2f129dd0 --- /dev/null +++ b/meta/global_status.go @@ -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) + } +} \ No newline at end of file diff --git a/meta/transaction_exception_code.go b/meta/transaction_exception_code.go new file mode 100644 index 00000000..a8d6ddb5 --- /dev/null +++ b/meta/transaction_exception_code.go @@ -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 +} \ No newline at end of file diff --git a/meta/transaction_role.go b/meta/transaction_role.go new file mode 100644 index 00000000..767633f2 --- /dev/null +++ b/meta/transaction_role.go @@ -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) + } +} \ No newline at end of file diff --git a/model/resource.go b/model/resource.go new file mode 100644 index 00000000..6a91e601 --- /dev/null +++ b/model/resource.go @@ -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 +} \ No newline at end of file diff --git a/model/set.go b/model/set.go new file mode 100644 index 00000000..7b7fd1e2 --- /dev/null +++ b/model/set.go @@ -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 +} + + diff --git a/protocal/codec/codec.go b/protocal/codec/codec.go new file mode 100644 index 00000000..0366cada --- /dev/null +++ b/protocal/codec/codec.go @@ -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 +} \ No newline at end of file diff --git a/protocal/codec/seata_decoder.go b/protocal/codec/seata_decoder.go new file mode 100644 index 00000000..fbd388cd --- /dev/null +++ b/protocal/codec/seata_decoder.go @@ -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 +} \ No newline at end of file diff --git a/protocal/codec/seata_encoder.go b/protocal/codec/seata_encoder.go new file mode 100644 index 00000000..1163ec0b --- /dev/null +++ b/protocal/codec/seata_encoder.go @@ -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() +} \ No newline at end of file diff --git a/protocal/constant.go b/protocal/constant.go new file mode 100644 index 00000000..c170cc3b --- /dev/null +++ b/protocal/constant.go @@ -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 +) diff --git a/protocal/heart_beat_message.go b/protocal/heart_beat_message.go new file mode 100644 index 00000000..a7acfc7d --- /dev/null +++ b/protocal/heart_beat_message.go @@ -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" + } +} \ No newline at end of file diff --git a/protocal/identify.go b/protocal/identify.go new file mode 100644 index 00000000..3c05aa8e --- /dev/null +++ b/protocal/identify.go @@ -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 +} \ No newline at end of file diff --git a/protocal/merged_message.go b/protocal/merged_message.go new file mode 100644 index 00000000..ec16a92f --- /dev/null +++ b/protocal/merged_message.go @@ -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 +} \ No newline at end of file diff --git a/protocal/message_type.go b/protocal/message_type.go new file mode 100644 index 00000000..733d2d08 --- /dev/null +++ b/protocal/message_type.go @@ -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 +) diff --git a/protocal/message_type_aware.go b/protocal/message_type_aware.go new file mode 100644 index 00000000..8bdcd7f4 --- /dev/null +++ b/protocal/message_type_aware.go @@ -0,0 +1,5 @@ +package protocal + +type MessageTypeAware interface { + GetTypeCode() int16 +} diff --git a/protocal/result_code.go b/protocal/result_code.go new file mode 100644 index 00000000..bc840814 --- /dev/null +++ b/protocal/result_code.go @@ -0,0 +1,18 @@ +package protocal + +type ResultCode byte + +const ( + + /** + * ResultCodeFailed result code. + */ + // ResultCodeFailed + ResultCodeFailed ResultCode = iota + + /** + * Success result code. + */ + // Success + ResultCodeSuccess +) diff --git a/protocal/rm.go b/protocal/rm.go new file mode 100644 index 00000000..62c3e2c5 --- /dev/null +++ b/protocal/rm.go @@ -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 +} \ No newline at end of file diff --git a/protocal/rpc_message.go b/protocal/rpc_message.go new file mode 100644 index 00000000..eb70b86b --- /dev/null +++ b/protocal/rpc_message.go @@ -0,0 +1,10 @@ +package protocal + +type RpcMessage struct { + Id int32 + MessageType byte + Codec byte + Compressor byte + HeadMap map[string]string + Body interface{} +} diff --git a/protocal/tm.go b/protocal/tm.go new file mode 100644 index 00000000..f962128b --- /dev/null +++ b/protocal/tm.go @@ -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 +} \ No newline at end of file diff --git a/protocal/transaction.go b/protocal/transaction.go new file mode 100644 index 00000000..1624b5a3 --- /dev/null +++ b/protocal/transaction.go @@ -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 +} \ No newline at end of file diff --git a/rm/resource_manager.go b/rm/resource_manager.go new file mode 100644 index 00000000..bcf3493a --- /dev/null +++ b/rm/resource_manager.go @@ -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 +} \ No newline at end of file diff --git a/tc/app/cmd/main.go b/tc/app/cmd/main.go new file mode 100644 index 00000000..df813ccf --- /dev/null +++ b/tc/app/cmd/main.go @@ -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) +} diff --git a/tc/app/profiles/dev/config.yml b/tc/app/profiles/dev/config.yml new file mode 100644 index 00000000..f4498442 --- /dev/null +++ b/tc/app/profiles/dev/config.yml @@ -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 \ No newline at end of file diff --git a/tc/config/getty_config.go b/tc/config/getty_config.go new file mode 100644 index 00000000..015c026c --- /dev/null +++ b/tc/config/getty_config.go @@ -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"` +} \ No newline at end of file diff --git a/tc/config/server_config.go b/tc/config/server_config.go new file mode 100644 index 00000000..a637a698 --- /dev/null +++ b/tc/config/server_config.go @@ -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 +} diff --git a/tc/config/store_config.go b/tc/config/store_config.go new file mode 100644 index 00000000..bf08a61d --- /dev/null +++ b/tc/config/store_config.go @@ -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, + } +} + diff --git a/tc/config/undo_config.go b/tc/config/undo_config.go new file mode 100644 index 00000000..ca757d65 --- /dev/null +++ b/tc/config/undo_config.go @@ -0,0 +1,5 @@ +package config + +type UndoConfig struct { + LogSaveDays int16 `default:"7" yaml:"log_save_days" json:"log_save_days,omitempty"` +} \ No newline at end of file diff --git a/tc/event/event_manager.go b/tc/event/event_manager.go new file mode 100644 index 00000000..8f0c3f4b --- /dev/null +++ b/tc/event/event_manager.go @@ -0,0 +1,11 @@ +package event + +type EventManager struct { + GlobalTransactionEventChannel chan GlobalTransactionEvent +} + +var EventBus EventManager + +func init() { + EventBus = EventManager{GlobalTransactionEventChannel:make(chan GlobalTransactionEvent,1000)} +} \ No newline at end of file diff --git a/tc/event/global_transaction_event.go b/tc/event/global_transaction_event.go new file mode 100644 index 00000000..a3698728 --- /dev/null +++ b/tc/event/global_transaction_event.go @@ -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 } \ No newline at end of file diff --git a/tc/holder/default_session_manager.go b/tc/holder/default_session_manager.go new file mode 100644 index 00000000..f766ddaf --- /dev/null +++ b/tc/holder/default_session_manager.go @@ -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 +} \ No newline at end of file diff --git a/tc/holder/default_session_manager_test.go b/tc/holder/default_session_manager_test.go new file mode 100644 index 00000000..b4a12621 --- /dev/null +++ b/tc/holder/default_session_manager_test.go @@ -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 +} \ No newline at end of file diff --git a/tc/holder/file_based_session_manager.go b/tc/holder/file_based_session_manager.go new file mode 100644 index 00000000..bc38e3a9 --- /dev/null +++ b/tc/holder/file_based_session_manager.go @@ -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 + } +} \ No newline at end of file diff --git a/tc/holder/file_based_session_manager_test.go b/tc/holder/file_based_session_manager_test.go new file mode 100644 index 00000000..21b108d0 --- /dev/null +++ b/tc/holder/file_based_session_manager_test.go @@ -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) + } +} \ No newline at end of file diff --git a/tc/holder/file_transaction_store_manager.go b/tc/holder/file_transaction_store_manager.go new file mode 100644 index 00000000..9f9a69f5 --- /dev/null +++ b/tc/holder/file_transaction_store_manager.go @@ -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) +} \ No newline at end of file diff --git a/tc/holder/session_holder.go b/tc/holder/session_holder.go new file mode 100644 index 00000000..2652289a --- /dev/null +++ b/tc/holder/session_holder.go @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/tc/holder/session_manager.go b/tc/holder/session_manager.go new file mode 100644 index 00000000..b3f301d5 --- /dev/null +++ b/tc/holder/session_manager.go @@ -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 +} \ No newline at end of file diff --git a/tc/holder/transaction_store_manager.go b/tc/holder/transaction_store_manager.go new file mode 100644 index 00000000..a0a2a448 --- /dev/null +++ b/tc/holder/transaction_store_manager.go @@ -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 +} + + diff --git a/tc/holder/transaction_write_store.go b/tc/holder/transaction_write_store.go new file mode 100644 index 00000000..81d8d4f4 --- /dev/null +++ b/tc/holder/transaction_write_store.go @@ -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 +} \ No newline at end of file diff --git a/tc/lock/lock_manager.go b/tc/lock/lock_manager.go new file mode 100644 index 00000000..145efbd4 --- /dev/null +++ b/tc/lock/lock_manager.go @@ -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 +} \ No newline at end of file diff --git a/tc/lock/lock_manager_test.go b/tc/lock/lock_manager_test.go new file mode 100644 index 00000000..c9197092 --- /dev/null +++ b/tc/lock/lock_manager_test.go @@ -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 +} \ No newline at end of file diff --git a/tc/lock/locker.go b/tc/lock/locker.go new file mode 100644 index 00000000..d0eb7ca1 --- /dev/null +++ b/tc/lock/locker.go @@ -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 +} \ No newline at end of file diff --git a/tc/lock/memory_lock.go b/tc/lock/memory_lock.go new file mode 100644 index 00000000..f423bd51 --- /dev/null +++ b/tc/lock/memory_lock.go @@ -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 +} \ No newline at end of file diff --git a/tc/lock/row_lock.go b/tc/lock/row_lock.go new file mode 100644 index 00000000..4059e05f --- /dev/null +++ b/tc/lock/row_lock.go @@ -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 +} \ No newline at end of file diff --git a/tc/model/session_condition.go b/tc/model/session_condition.go new file mode 100644 index 00000000..78e73908 --- /dev/null +++ b/tc/model/session_condition.go @@ -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 +} \ No newline at end of file diff --git a/tc/server/default_coordinator.go b/tc/server/default_coordinator.go new file mode 100644 index 00000000..df3f8261 --- /dev/null +++ b/tc/server/default_coordinator.go @@ -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() +} \ No newline at end of file diff --git a/tc/server/default_core.go b/tc/server/default_core.go new file mode 100644 index 00000000..0feb5842 --- /dev/null +++ b/tc/server/default_core.go @@ -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) +} \ No newline at end of file diff --git a/tc/server/getty_session_manager.go b/tc/server/getty_session_manager.go new file mode 100644 index 00000000..127add3e --- /dev/null +++ b/tc/server/getty_session_manager.go @@ -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 +} \ No newline at end of file diff --git a/tc/server/readwriter.go b/tc/server/readwriter.go new file mode 100644 index 00000000..e5acff50 --- /dev/null +++ b/tc/server/readwriter.go @@ -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" +) + +/** + *
+ * 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                                                  |
+ * |                                                                                               |
+ * |                                        ... ...                                                |
+ * +-----------------------------------------------------------------------------------------------+
+ * 
+ *

+ *

  • Full Length: include all data
  • + *
  • Head Length: include head data from magic code to head map.
  • + *
  • Body Length: Full Length - Head Length
  • + *

    + * 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() +} \ No newline at end of file diff --git a/tc/server/rpc_context.go b/tc/server/rpc_context.go new file mode 100644 index 00000000..11ac2eb7 --- /dev/null +++ b/tc/server/rpc_context.go @@ -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 + + /** + * + */ + ClientIDHolderMap *sync.Map + + /** + * + */ + 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 +} \ No newline at end of file diff --git a/tc/server/server.go b/tc/server/server.go new file mode 100644 index 00000000..1aecd880 --- /dev/null +++ b/tc/server/server.go @@ -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() +} diff --git a/tc/server/server_message_listener.go b/tc/server/server_message_listener.go new file mode 100644 index 00000000..56fff607 --- /dev/null +++ b/tc/server/server_message_listener.go @@ -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) +} \ No newline at end of file diff --git a/tc/server/server_message_sender.go b/tc/server/server_message_sender.go new file mode 100644 index 00000000..94f7f3dd --- /dev/null +++ b/tc/server/server_message_sender.go @@ -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 +} diff --git a/tc/server/tc_inbound_handler.go b/tc/server/tc_inbound_handler.go new file mode 100644 index 00000000..e8395c8b --- /dev/null +++ b/tc/server/tc_inbound_handler.go @@ -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 +} \ No newline at end of file diff --git a/tc/server/transaction_coordinator.go b/tc/server/transaction_coordinator.go new file mode 100644 index 00000000..ca1e7d11 --- /dev/null +++ b/tc/server/transaction_coordinator.go @@ -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 +} diff --git a/tc/session/branch_session.go b/tc/session/branch_session.go new file mode 100644 index 00000000..881c562a --- /dev/null +++ b/tc/session/branch_session.go @@ -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 +} diff --git a/tc/session/branch_session_test.go b/tc/session/branch_session_test.go new file mode 100644 index 00000000..9df8d23a --- /dev/null +++ b/tc/session/branch_session_test.go @@ -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 +} \ No newline at end of file diff --git a/tc/session/global_session.go b/tc/session/global_session.go new file mode 100644 index 00000000..81094704 --- /dev/null +++ b/tc/session/global_session.go @@ -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 +} \ No newline at end of file diff --git a/tc/session/global_session_test.go b/tc/session/global_session_test.go new file mode 100644 index 00000000..d28e936d --- /dev/null +++ b/tc/session/global_session_test.go @@ -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 +} \ No newline at end of file diff --git a/tc/session/session_storable.go b/tc/session/session_storable.go new file mode 100644 index 00000000..b2a8b452 --- /dev/null +++ b/tc/session/session_storable.go @@ -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) +} diff --git a/tm/transaction_manager.go b/tm/transaction_manager.go new file mode 100644 index 00000000..635444ee --- /dev/null +++ b/tm/transaction_manager.go @@ -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) +} diff --git a/util/hashcode.go b/util/hashcode.go new file mode 100644 index 00000000..00055f00 --- /dev/null +++ b/util/hashcode.go @@ -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())) +} diff --git a/util/time.go b/util/time.go new file mode 100644 index 00000000..86419363 --- /dev/null +++ b/util/time.go @@ -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()) +} diff --git a/util/uuid_generator.go b/util/uuid_generator.go new file mode 100644 index 00000000..ffb78713 --- /dev/null +++ b/util/uuid_generator.go @@ -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 +} \ No newline at end of file