Browse Source

0.0.0

tags/v1.0.0-rc2
liuxiaomin 5 years ago
parent
commit
d7dfc72f11
72 changed files with 8068 additions and 0 deletions
  1. +19
    -0
      .gitignore
  2. +13
    -0
      README.md
  3. +26
    -0
      common/xid.go
  4. +1
    -0
      docs/seata-golang.svg
  5. +17
    -0
      go.mod
  6. +84
    -0
      go.sum
  7. +186
    -0
      logging/logging.go
  8. +7
    -0
      logging/logging_test.go
  9. +102
    -0
      meta/branch_status.go
  10. +36
    -0
      meta/branch_type.go
  11. +142
    -0
      meta/global_status.go
  12. +110
    -0
      meta/transaction_exception_code.go
  13. +33
    -0
      meta/transaction_role.go
  14. +28
    -0
      model/resource.go
  15. +69
    -0
      model/set.go
  16. +243
    -0
      protocal/codec/codec.go
  17. +773
    -0
      protocal/codec/seata_decoder.go
  18. +560
    -0
      protocal/codec/seata_encoder.go
  19. +13
    -0
      protocal/constant.go
  20. +16
    -0
      protocal/heart_beat_message.go
  21. +26
    -0
      protocal/identify.go
  22. +17
    -0
      protocal/merged_message.go
  23. +117
    -0
      protocal/message_type.go
  24. +5
    -0
      protocal/message_type_aware.go
  25. +18
    -0
      protocal/result_code.go
  26. +18
    -0
      protocal/rm.go
  27. +10
    -0
      protocal/rpc_message.go
  28. +17
    -0
      protocal/tm.go
  29. +227
    -0
      protocal/transaction.go
  30. +109
    -0
      rm/resource_manager.go
  31. +20
    -0
      tc/app/cmd/main.go
  32. +28
    -0
      tc/app/profiles/dev/config.yml
  33. +38
    -0
      tc/config/getty_config.go
  34. +104
    -0
      tc/config/server_config.go
  35. +58
    -0
      tc/config/store_config.go
  36. +5
    -0
      tc/config/undo_config.go
  37. +11
    -0
      tc/event/event_manager.go
  38. +41
    -0
      tc/event/global_transaction_event.go
  39. +66
    -0
      tc/holder/default_session_manager.go
  40. +87
    -0
      tc/holder/default_session_manager_test.go
  41. +215
    -0
      tc/holder/file_based_session_manager.go
  42. +132
    -0
      tc/holder/file_based_session_manager_test.go
  43. +232
    -0
      tc/holder/file_transaction_store_manager.go
  44. +93
    -0
      tc/holder/session_holder.go
  45. +181
    -0
      tc/holder/session_manager.go
  46. +135
    -0
      tc/holder/transaction_store_manager.go
  47. +53
    -0
      tc/holder/transaction_write_store.go
  48. +56
    -0
      tc/lock/lock_manager.go
  49. +152
    -0
      tc/lock/lock_manager_test.go
  50. +16
    -0
      tc/lock/locker.go
  51. +188
    -0
      tc/lock/memory_lock.go
  52. +76
    -0
      tc/lock/row_lock.go
  53. +11
    -0
      tc/model/session_condition.go
  54. +659
    -0
      tc/server/default_coordinator.go
  55. +584
    -0
      tc/server/default_core.go
  56. +372
    -0
      tc/server/getty_session_manager.go
  57. +190
    -0
      tc/server/readwriter.go
  58. +136
    -0
      tc/server/rpc_context.go
  59. +121
    -0
      tc/server/server.go
  60. +16
    -0
      tc/server/server_message_listener.go
  61. +78
    -0
      tc/server/server_message_sender.go
  62. +14
    -0
      tc/server/tc_inbound_handler.go
  63. +73
    -0
      tc/server/transaction_coordinator.go
  64. +230
    -0
      tc/session/branch_session.go
  65. +37
    -0
      tc/session/branch_session_test.go
  66. +297
    -0
      tc/session/global_session.go
  67. +31
    -0
      tc/session/global_session_test.go
  68. +17
    -0
      tc/session/session_storable.go
  69. +59
    -0
      tm/transaction_manager.go
  70. +35
    -0
      util/hashcode.go
  71. +33
    -0
      util/time.go
  72. +46
    -0
      util/uuid_generator.go

+ 19
- 0
.gitignore View File

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

+ 13
- 0
README.md View File

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

+ 26
- 0
common/xid.go View File

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

+ 1
- 0
docs/seata-golang.svg
File diff suppressed because it is too large
View File


+ 17
- 0
go.mod View File

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

+ 84
- 0
go.sum View File

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

+ 186
- 0
logging/logging.go View File

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

+ 7
- 0
logging/logging_test.go View File

@@ -0,0 +1,7 @@
package logging

import "testing"

func TestSeataLogger_Info(t *testing.T) {
Logger.Info("there is a bug")
}

+ 102
- 0
meta/branch_status.go View File

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

+ 36
- 0
meta/branch_type.go View File

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

+ 142
- 0
meta/global_status.go View File

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

+ 110
- 0
meta/transaction_exception_code.go View File

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

+ 33
- 0
meta/transaction_role.go View File

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

+ 28
- 0
model/resource.go View File

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

+ 69
- 0
model/set.go View File

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



+ 243
- 0
protocal/codec/codec.go View File

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

+ 773
- 0
protocal/codec/seata_decoder.go View File

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

+ 560
- 0
protocal/codec/seata_encoder.go View File

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

+ 13
- 0
protocal/constant.go View File

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

+ 16
- 0
protocal/heart_beat_message.go View File

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

+ 26
- 0
protocal/identify.go View File

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

+ 17
- 0
protocal/merged_message.go View File

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

+ 117
- 0
protocal/message_type.go View File

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

+ 5
- 0
protocal/message_type_aware.go View File

@@ -0,0 +1,5 @@
package protocal

type MessageTypeAware interface {
GetTypeCode() int16
}

+ 18
- 0
protocal/result_code.go View File

@@ -0,0 +1,18 @@
package protocal

type ResultCode byte

const (

/**
* ResultCodeFailed result code.
*/
// ResultCodeFailed
ResultCodeFailed ResultCode = iota

/**
* Success result code.
*/
// Success
ResultCodeSuccess
)

+ 18
- 0
protocal/rm.go View File

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

+ 10
- 0
protocal/rpc_message.go View File

@@ -0,0 +1,10 @@
package protocal

type RpcMessage struct {
Id int32
MessageType byte
Codec byte
Compressor byte
HeadMap map[string]string
Body interface{}
}

+ 17
- 0
protocal/tm.go View File

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

+ 227
- 0
protocal/transaction.go View File

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

+ 109
- 0
rm/resource_manager.go View File

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

+ 20
- 0
tc/app/cmd/main.go View File

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

+ 28
- 0
tc/app/profiles/dev/config.yml View File

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

+ 38
- 0
tc/config/getty_config.go View File

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

+ 104
- 0
tc/config/server_config.go View File

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

+ 58
- 0
tc/config/store_config.go View File

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


+ 5
- 0
tc/config/undo_config.go View File

@@ -0,0 +1,5 @@
package config

type UndoConfig struct {
LogSaveDays int16 `default:"7" yaml:"log_save_days" json:"log_save_days,omitempty"`
}

+ 11
- 0
tc/event/event_manager.go View File

@@ -0,0 +1,11 @@
package event

type EventManager struct {
GlobalTransactionEventChannel chan GlobalTransactionEvent
}

var EventBus EventManager

func init() {
EventBus = EventManager{GlobalTransactionEventChannel:make(chan GlobalTransactionEvent,1000)}
}

+ 41
- 0
tc/event/global_transaction_event.go View File

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

+ 66
- 0
tc/holder/default_session_manager.go View File

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

+ 87
- 0
tc/holder/default_session_manager_test.go View File

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

+ 215
- 0
tc/holder/file_based_session_manager.go View File

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

+ 132
- 0
tc/holder/file_based_session_manager_test.go View File

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

+ 232
- 0
tc/holder/file_transaction_store_manager.go View File

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

+ 93
- 0
tc/holder/session_holder.go View File

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

+ 181
- 0
tc/holder/session_manager.go View File

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

+ 135
- 0
tc/holder/transaction_store_manager.go View File

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



+ 53
- 0
tc/holder/transaction_write_store.go View File

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

+ 56
- 0
tc/lock/lock_manager.go View File

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

+ 152
- 0
tc/lock/lock_manager_test.go View File

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

+ 16
- 0
tc/lock/locker.go View File

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

+ 188
- 0
tc/lock/memory_lock.go View File

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

+ 76
- 0
tc/lock/row_lock.go View File

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

+ 11
- 0
tc/model/session_condition.go View File

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

+ 659
- 0
tc/server/default_coordinator.go View File

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

+ 584
- 0
tc/server/default_core.go View File

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

+ 372
- 0
tc/server/getty_session_manager.go View File

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

+ 190
- 0
tc/server/readwriter.go View File

@@ -0,0 +1,190 @@
package server

import (
"bytes"
"github.com/dubbogo/getty"
"github.com/pkg/errors"
"github.com/dk-lockdown/seata-golang/protocal"
"github.com/dk-lockdown/seata-golang/protocal/codec"
"vimagination.zapto.org/byteio"
)

/**
* <pre>
* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
* +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
* | magic |Proto| Full length | Head | Msg |Seria|Compr| RequestId |
* | code |colVer| (head+body) | Length |Type |lizer|ess | |
* +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
* | |
* | Head Map [Optional] |
* +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
* | |
* | body |
* | |
* | ... ... |
* +-----------------------------------------------------------------------------------------------+
* </pre>
* <p>
* <li>Full Length: include all data </li>
* <li>Head Length: include head data from magic code to head map. </li>
* <li>Body Length: Full Length - Head Length</li>
* </p>
* https://github.com/seata/seata/issues/893
*/

var (
RpcServerPkgHandler = &RpcServerPackageHandler{}
)

type RpcServerPackageHandler struct{}

func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {
r := byteio.BigEndianReader{Reader:bytes.NewReader(data)}

b0,_ := r.ReadByte()
b1,_ := r.ReadByte()

if b0 != protocal.MAGIC_CODE_BYTES[0] || b1 != protocal.MAGIC_CODE_BYTES[1] {
return nil,0,errors.Errorf("Unknown magic code: %b,%b",b0,b1)
}

r.ReadByte()
// TODO check version compatible here

fullLength,_,_ := r.ReadInt32()
headLength,_,_ := r.ReadInt16()
messageType,_ := r.ReadByte()
codecType,_ := r.ReadByte()
compressorType,_ := r.ReadByte()
requestId,_,_ := r.ReadInt32()

rpcMessage := protocal.RpcMessage{
Codec:codecType,
Id:requestId,
Compressor:compressorType,
MessageType:messageType,
}

headMapLength := headLength - protocal.V1_HEAD_LENGTH
if headMapLength > 0 {
rpcMessage.HeadMap = headMapDecode(data[protocal.V1_HEAD_LENGTH+1:headMapLength])
}

if messageType == protocal.MSGTYPE_HEARTBEAT_REQUEST {
rpcMessage.Body = protocal.HeartBeatMessagePing
} else if messageType == protocal.MSGTYPE_HEARTBEAT_RESPONSE {
rpcMessage.Body = protocal.HeartBeatMessagePong
} else {
bodyLength := fullLength - int32(headLength)
if bodyLength > 0 {
//todo compress

msg,_ := codec.MessageDecoder(codecType,data[headLength:])
rpcMessage.Body = msg
}
}

return rpcMessage, int(fullLength), nil
}

func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) {
var result = make([]byte,0)
msg := pkg.(protocal.RpcMessage)

fullLength := protocal.V1_HEAD_LENGTH
headLength := protocal.V1_HEAD_LENGTH

var b bytes.Buffer
w := byteio.BigEndianWriter{Writer: &b}

result = append(result, protocal.MAGIC_CODE_BYTES[:2]...)
result = append(result, protocal.VERSION)

w.WriteByte(msg.MessageType)
w.WriteByte(msg.Codec)
w.WriteByte(msg.Compressor)
w.WriteInt32(msg.Id)

if msg.HeadMap != nil && len(msg.HeadMap) > 0 {
headMapBytes,headMapLength := headMapEncode(msg.HeadMap)
headLength += headMapLength
fullLength += headMapLength
w.Write(headMapBytes)
}

if msg.MessageType != protocal.MSGTYPE_HEARTBEAT_REQUEST &&
msg.MessageType != protocal.MSGTYPE_HEARTBEAT_RESPONSE {
bodyBytes := codec.MessageEncoder(msg.Codec,msg.Body)
fullLength += len(bodyBytes)
w.Write(bodyBytes)
}

fullLen := int32(fullLength)
headLen := int16(headLength)
result = append(result, []byte{ byte(fullLen>>26),byte(fullLen>>16),byte(fullLen>>8),byte(fullLen) }...)
result = append(result, []byte{ byte(headLen>>8),byte(headLen) }...)
result = append(result,b.Bytes()...)

return result, nil
}


func headMapDecode(data []byte) map[string]string {
mp := make(map[string]string)
size := len(data)
if size == 0 {
return mp
}
r := byteio.BigEndianReader{Reader:bytes.NewReader(data)}

readLength := 0
for {
if readLength >= size { break }

var key, value string
lengthK,_,_ := r.ReadUint16()
if lengthK < 0 {
break
} else if lengthK == 0 {
key = ""
} else {
key,_,_ = r.ReadString(int(lengthK))
}

lengthV,_,_ := r.ReadUint16()
if lengthV < 0 {
break
} else if lengthV == 0 {
value = ""
} else {
value,_,_ = r.ReadString(int(lengthV))
}

mp[key] = value
readLength += int(lengthK + lengthV)
}
return mp
}

func headMapEncode(data map[string]string) ([]byte,int) {
var b bytes.Buffer

w := byteio.BigEndianWriter{Writer: &b}
for k,v := range data{
if k == "" {
w.WriteUint16(0)
} else {
w.WriteUint16(uint16(len(k)))
w.WriteString(k)
}

if v == "" {
w.WriteUint16(0)
} else {
w.WriteUint16(uint16(len(v)))
w.WriteString(v)
}
}
return b.Bytes(),b.Len()
}

+ 136
- 0
tc/server/rpc_context.go View File

@@ -0,0 +1,136 @@
package server

import (
"errors"
"github.com/dubbogo/getty"
"github.com/dk-lockdown/seata-golang/meta"
"github.com/dk-lockdown/seata-golang/model"
"strconv"
"strings"
"sync"
)

const IpPortSplitChar = ":"

type RpcContext struct {
ClientRole meta.TransactionRole
Version string
ApplicationId string
TransactionServiceGroup string
ClientId string
session getty.Session
ResourceSets *model.Set

/**
* <getty.Session,*RpcContext>
*/
ClientIDHolderMap *sync.Map

/**
* <int,RpcContext>
*/
ClientTMHolderMap *sync.Map

/**
* resourceId -> int -> RpcContext>
*/
ClientRMHolderMap *sync.Map
}

func (context *RpcContext) Release() {
clientPort := getClientPortFromGettySession(context.session)
if context.ClientIDHolderMap != nil {
context.ClientIDHolderMap = nil
}
if context.ClientRole == meta.TMROLE && context.ClientTMHolderMap != nil {
context.ClientTMHolderMap.Delete(clientPort)
context.ClientTMHolderMap = nil
}
if context.ClientRole == meta.RMROLE && context.ClientRMHolderMap != nil {
context.ClientRMHolderMap.Range(func (key interface{}, value interface{}) bool {
m := value.(*sync.Map)
m.Delete(clientPort)
return true
})
context.ClientRMHolderMap = nil
}
if context.ResourceSets != nil {
context.ResourceSets.Clear()
}
}

func (context *RpcContext) HoldInClientGettySessions(clientTMHolderMap *sync.Map) error {
if context.ClientTMHolderMap != nil {
return errors.New("illegal state")
}
context.ClientTMHolderMap = clientTMHolderMap
clientPort := getClientPortFromGettySession(context.session)
context.ClientTMHolderMap.Store(clientPort,context)
return nil
}

func (context *RpcContext) HoldInIdentifiedGettySessions(clientIDHolderMap *sync.Map) error {
if context.ClientIDHolderMap != nil {
return errors.New("illegal state")
}
context.ClientIDHolderMap = clientIDHolderMap
context.ClientIDHolderMap.Store(context.session,context)
return nil
}

func (context *RpcContext) HoldInResourceManagerGettySessions(resourceId string,portMap *sync.Map) {
if context.ClientRMHolderMap == nil {
context.ClientRMHolderMap = &sync.Map{}
}
clientPort := getClientPortFromGettySession(context.session)
portMap.Store(clientPort,context)
context.ClientRMHolderMap.Store(resourceId,portMap)
}

func (context *RpcContext) HoldInResourceManagerGettySessionsWithoutPortMap(resourceId string,clientPort int) {
if context.ClientRMHolderMap == nil {
context.ClientRMHolderMap = &sync.Map{}
}
portMap,_ := context.ClientRMHolderMap.LoadOrStore(resourceId,&sync.Map{})
pm := portMap.(*sync.Map)
pm.Store(clientPort,context)
}

func (context *RpcContext) AddResource(resource string) {
if resource != "" {
if context.ResourceSets == nil {
context.ResourceSets = model.NewSet()
}
context.ResourceSets.Add(resource)
}
}

func (context *RpcContext) AddResources(resources *model.Set) {
if resources != nil {
if context.ResourceSets == nil {
context.ResourceSets = model.NewSet()
}
for _,resource := range resources.List() {
context.ResourceSets.Add(resource)
}
}
}

func getClientIpFromGettySession(session getty.Session) string {
clientIp := session.RemoteAddr()
if strings.Contains(clientIp,IpPortSplitChar) {
idx := strings.Index(clientIp,IpPortSplitChar)
clientIp = clientIp[:idx]
}
return clientIp
}

func getClientPortFromGettySession(session getty.Session) int {
address := session.RemoteAddr()
port := 0
if strings.Contains(address,IpPortSplitChar) {
idx := strings.LastIndex(address,IpPortSplitChar)
port,_ = strconv.Atoi(address[idx+1:])
}
return port
}

+ 121
- 0
tc/server/server.go View File

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

+ 16
- 0
tc/server/server_message_listener.go View File

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

+ 78
- 0
tc/server/server_message_sender.go View File

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

+ 14
- 0
tc/server/tc_inbound_handler.go View File

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

+ 73
- 0
tc/server/transaction_coordinator.go View File

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

+ 230
- 0
tc/session/branch_session.go View File

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

+ 37
- 0
tc/session/branch_session_test.go View File

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

+ 297
- 0
tc/session/global_session.go View File

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

+ 31
- 0
tc/session/global_session_test.go View File

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

+ 17
- 0
tc/session/session_storable.go View File

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

+ 59
- 0
tm/transaction_manager.go View File

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

+ 35
- 0
util/hashcode.go View File

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

+ 33
- 0
util/time.go View File

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

+ 46
- 0
util/uuid_generator.go View File

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

Loading…
Cancel
Save