0.0.0
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7391b20
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+.idea/
+vendor/
+root.data
+root.data.1
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e46a022
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# seata-golang
+
+### 一个朴素的想法
+作为一个刚入 Golang 坑的微服务普通开发者来讲,很容易产生一个朴素的想法,希望 Golang 微服务也有分布式事务解决方案。我们注意到阿里开源了 Java 版的分布式事务解决方案 Seata,本项目尝试将 Java 版的 Seata 改写一个 Golang 的版本。
+在 Seata 没有 Golang 版本 client sdk 的情况下,Golang 版本的 TC Server 使用了和 Java 版 Seata 一样的通信协议,方便调试。
+目前,基于内存的 TC Server 已实现。希望有同样朴素想法的开发者加入我们一起完善 Golang 版本的分布式事务解决方案。
+
+### todo list
+- [X] Memory Session Manager
+- [ ] DB Session Manager
+- [ ] ETCD Session Manager
+- [ ] TC
+- [ ] RM
\ No newline at end of file
diff --git a/common/xid.go b/common/xid.go
new file mode 100644
index 0000000..8603ca3
--- /dev/null
+++ b/common/xid.go
@@ -0,0 +1,26 @@
+package common
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type XId struct {
+ Port int
+ IpAddress string
+}
+
+var XID = &XId{}
+
+func (xId *XId) GenerateXID(tranId int64) string {
+ return fmt.Sprintf("%s:%d:%d",xId.IpAddress,xId.Port,tranId)
+}
+
+func (xId *XId) GetTransactionId(xid string) int64 {
+ if xid == "" { return -1 }
+
+ idx := strings.LastIndex(xid,":")
+ tranId,_ := strconv.ParseInt(xid[idx:],10,64)
+ return tranId
+}
diff --git a/docs/seata-golang.svg b/docs/seata-golang.svg
new file mode 100644
index 0000000..0e5b662
--- /dev/null
+++ b/docs/seata-golang.svg
@@ -0,0 +1 @@
+<svg id="kity_svg_6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="729" height="1334" style="background: rgb(251, 251, 251); visibility: visible;" viewBox="0 0 729 1334"><defs id="kity_defs_7"><linearGradient id="kity_linearGradient_17" x1="0" y1="0" x2="0" y2="1"><stop id="kity_stop_18" offset="0" stop-color="rgb(255, 255, 255)"></stop><stop id="kity_stop_19" offset="1" stop-color="rgb(204, 204, 204)"></stop></linearGradient><marker id="kity_marker_2" orient="auto" refX="6" refY="0" viewBox="-7 -7 14 14" markerWidth="7" markerHeight="7" markerUnits="userSpaceOnUse"><path id="kity_path_3" fill="rgb(115, 161, 191)" stroke="none" d="M6,0A6,6,0,1,1,-6,0A6,6,0,1,1,6,0"></path></marker></defs><g id="kity_g_8"><g id="minder1" text-rendering="optimize-speed"><g id="minder_connect_group1"><path id="kity_path_31" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,634,0,0,1,291.5,33.5"></path><path id="kity_path_33" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M370.5,33.5C390.5,33.5,390.5,33.5,410.5,33.5"></path><path id="kity_path_35" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,568,0,0,1,291.5,99.5"></path><path id="kity_path_37" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M377.5,99.5C397.5,99.5,397.5,99.5,417.5,99.5"></path><path id="kity_path_39" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,172,0,0,1,291.5,495.5"></path><path id="kity_path_45" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,190.5,387.5,190.5"></path><path id="kity_path_47" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M446.5,190.5C466.5,190.5,466.5,164.5,486.5,164.5"></path><path id="kity_path_49" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M446.5,190.5C466.5,190.5,466.5,216.5,486.5,216.5"></path><path id="kity_path_51" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,294.5,387.5,294.5"></path><path id="kity_path_53" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M444.5,294.5C464.5,294.5,464.5,294.5,484.5,294.5"></path><path id="kity_path_55" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M556.5,294.5C576.5,294.5,576.5,268.5,596.5,268.5"></path><path id="kity_path_57" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M556.5,294.5C576.5,294.5,576.5,320.5,596.5,320.5"></path><path id="kity_path_59" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,398.5,387.5,398.5"></path><path id="kity_path_61" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M473.5,398.5C493.5,398.5,493.5,372.5,513.5,372.5"></path><path id="kity_path_63" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M473.5,398.5C493.5,398.5,493.5,424.5,513.5,424.5"></path><path id="kity_path_65" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,528.5,387.5,528.5"></path><path id="kity_path_67" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M431.5,528.5C451.5,528.5,451.5,528.5,471.5,528.5"></path><path id="kity_path_71" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M544.5,528.5C564.5,528.5,564.5,476.5,584.5,476.5"></path><path id="kity_path_73" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M544.5,528.5C564.5,528.5,564.5,528.5,584.5,528.5"></path><path id="kity_path_75" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M544.5,528.5C564.5,528.5,564.5,580.5,584.5,580.5"></path><path id="kity_path_77" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,632.5,387.5,632.5"></path><path id="kity_path_79" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M445.5,632.5C465.5,632.5,465.5,632.5,485.5,632.5"></path><path id="kity_path_81" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M347.5,495.5C367.5,495.5,367.5,801.5,387.5,801.5"></path><path id="kity_path_83" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M444.5,801.5C464.5,801.5,464.5,736.5,484.5,736.5"></path><path id="kity_path_87" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M557.5,736.5C577.5,736.5,577.5,684.5,597.5,684.5"></path><path id="kity_path_89" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M557.5,736.5C577.5,736.5,577.5,736.5,597.5,736.5"></path><path id="kity_path_91" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M557.5,736.5C577.5,736.5,577.5,788.5,597.5,788.5"></path><path id="kity_path_93" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M444.5,801.5C464.5,801.5,464.5,866.5,484.5,866.5"></path><path id="kity_path_95" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M601.5,866.5C621.5,866.5,621.5,840.5,641.5,840.5"></path><path id="kity_path_97" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M601.5,866.5C621.5,866.5,621.5,892.5,641.5,892.5"></path><path id="kity_path_99" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,338,0,0,0,291.5,1005.5"></path><path id="kity_path_101" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M350.5,1005.5C370.5,1005.5,370.5,953.5,390.5,953.5"></path><path id="kity_path_103" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M350.5,1005.5C370.5,1005.5,370.5,1005.5,390.5,1005.5"></path><path id="kity_path_105" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M350.5,1005.5C370.5,1005.5,370.5,1057.5,390.5,1057.5"></path><path id="kity_path_107" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,454,0,0,0,291.5,1121.5"></path><path id="kity_path_109" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,544,0,0,0,291.5,1211.5"></path><path id="kity_path_111" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M385.5,1211.5C405.5,1211.5,405.5,1211.5,425.5,1211.5"></path><path id="kity_path_113" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M498.5,1211.5C518.5,1211.5,518.5,1185.5,538.5,1185.5"></path><path id="kity_path_115" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M498.5,1211.5C518.5,1211.5,518.5,1237.5,538.5,1237.5"></path><path id="kity_path_117" fill="none" stroke="rgb(115, 161, 191)" stroke-width="1" d="M97,667.5A194.5,634,0,0,0,291.5,1301.5"></path></g><g id="minder_node1"><path id="node_outline1" fill="rgb(115, 161, 191)" stroke="rgb(115, 161, 191)" d="M25.5,647.5h143a5,5,0,0,1,5,5v30a5,5,0,0,1,-5,5h-143a5,5,0,0,1,-5,-5v-30a5,5,0,0,1,5,-5z" stroke-width="3"></path><g id="node_text1" fill="white"><text id="kity_text_22" text-rendering="inherit" font-size="16" dy=".8em" y="659.5" x="44.5">seata-golang</text></g></g><g id="kity_g_12"><path id="kity_path_13" fill="none" stroke="none" d="M44.5,667.5h0v0h0z"></path></g><g id="kity_g_14"><path id="kity_path_15" fill="none" stroke="none" d="M44.5,667.5h0v0h0z"></path><path id="kity_path_16" fill="none" stroke="none"></path></g><g id="minder_node2"><g id="node_expander2" style="cursor: pointer;"><path id="kity_path_254" fill="white" stroke="gray" d="M292.5,33.5A6,6,0,1,1,280.5,33.5A6,6,0,1,1,292.5,33.5"></path><path id="kity_path_255" fill="none" stroke="gray" d="M282,33.5L291,33.5"></path></g><path id="node_outline3" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,20.5h71a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-71a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text3" fill="black"><text id="kity_text_121" text-rendering="inherit" font-size="14" dy=".8em" y="26.5" x="313.5">meta</text></g></g><g id="minder_node3"><g id="node_expander1" style="cursor: pointer;" display="none"><path id="kity_path_251" fill="white" stroke="gray" d="M410.5,33.5A6,6,0,1,1,398.5,33.5A6,6,0,1,1,410.5,33.5"></path><path id="kity_path_252" fill="none" stroke="gray"></path></g><path id="node_outline2" fill="none" stroke="none" d="M415.5,22.5h46a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-46a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text2" fill="black"><text id="kity_text_119" text-rendering="inherit" font-size="12" dy=".8em" y="27.5" x="420.5">枚举等</text></g></g><g id="minder_node4"><g id="node_expander4" style="cursor: pointer;"><path id="kity_path_260" fill="white" stroke="gray" d="M292.5,99.5A6,6,0,1,1,280.5,99.5A6,6,0,1,1,292.5,99.5"></path><path id="kity_path_261" fill="none" stroke="gray" d="M282,99.5L291,99.5"></path></g><path id="node_outline5" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,86.5h78a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-78a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text5" fill="black"><text id="kity_text_125" text-rendering="inherit" font-size="14" dy=".8em" y="92.5" x="313.5">model</text></g></g><g id="minder_node5"><g id="node_expander3" style="cursor: pointer;" display="none"><path id="kity_path_257" fill="white" stroke="gray" d="M417.5,99.5A6,6,0,1,1,405.5,99.5A6,6,0,1,1,417.5,99.5"></path><path id="kity_path_258" fill="none" stroke="gray"></path></g><path id="node_outline4" fill="none" stroke="none" d="M422.5,88.5h60a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-60a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text4" fill="black"><text id="kity_text_123" text-rendering="inherit" font-size="12" dy=".8em" y="93.5" x="427.5">resource</text></g></g><g id="minder_node6"><g id="node_expander34" style="cursor: pointer;"><path id="kity_path_350" fill="white" stroke="gray" d="M292.5,495.5A6,6,0,1,1,280.5,495.5A6,6,0,1,1,292.5,495.5"></path><path id="kity_path_351" fill="none" stroke="gray" d="M282,495.5L291,495.5"></path></g><path id="node_outline35" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,482.5h48a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-48a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text35" fill="black"><text id="kity_text_185" text-rendering="inherit" font-size="14" dy=".8em" y="488.5" x="313.5">tc</text></g></g><g id="minder_node9"><g id="node_expander9" style="cursor: pointer;"><path id="kity_path_275" fill="white" stroke="gray" d="M387.5,190.5A6,6,0,1,1,375.5,190.5A6,6,0,1,1,387.5,190.5"></path><path id="kity_path_276" fill="none" stroke="gray" d="M377,190.5L386,190.5"></path></g><path id="node_outline10" fill="none" stroke="none" d="M392.5,179.5h49a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-49a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z" stroke-width="3"></path><g id="node_text10" fill="black"><text id="kity_text_135" text-rendering="inherit" font-size="12" dy=".8em" y="184.5" x="397.5">session</text></g></g><g id="minder_node10"><g id="node_expander7" style="cursor: pointer;" display="none"><path id="kity_path_269" fill="white" stroke="gray" d="M486.5,164.5A6,6,0,1,1,474.5,164.5A6,6,0,1,1,486.5,164.5"></path><path id="kity_path_270" fill="none" stroke="gray"></path></g><path id="node_outline8" fill="none" stroke="none" d="M491.5,153.5h95a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-95a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text8" fill="black"><text id="kity_text_131" text-rendering="inherit" font-size="12" dy=".8em" y="158.5" x="496.5">branch session</text></g></g><g id="minder_node11"><g id="node_expander8" style="cursor: pointer;" display="none"><path id="kity_path_272" fill="white" stroke="gray" d="M486.5,216.5A6,6,0,1,1,474.5,216.5A6,6,0,1,1,486.5,216.5"></path><path id="kity_path_273" fill="none" stroke="gray"></path></g><path id="node_outline9" fill="none" stroke="none" d="M491.5,205.5h89a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-89a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text9" fill="black"><text id="kity_text_133" text-rendering="inherit" font-size="12" dy=".8em" y="210.5" x="496.5">global session</text></g></g><g id="minder_node12"><g id="node_expander13" style="cursor: pointer;"><path id="kity_path_287" fill="white" stroke="gray" d="M387.5,294.5A6,6,0,1,1,375.5,294.5A6,6,0,1,1,387.5,294.5"></path><path id="kity_path_288" fill="none" stroke="gray" d="M377,294.5L386,294.5"></path></g><path id="node_outline14" fill="none" stroke="none" d="M392.5,283.5h47a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-47a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text14" fill="black"><text id="kity_text_143" text-rendering="inherit" font-size="12" dy=".8em" y="288.5" x="397.5">config</text></g></g><g id="minder_node13"><g id="node_expander12" style="cursor: pointer;"><path id="kity_path_284" fill="white" stroke="gray" d="M484.5,294.5A6,6,0,1,1,472.5,294.5A6,6,0,1,1,484.5,294.5"></path><path id="kity_path_285" fill="none" stroke="gray" d="M474,294.5L483,294.5"></path></g><path id="node_outline13" fill="none" stroke="none" d="M489.5,283.5h62a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-62a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text13" fill="black"><text id="kity_text_141" text-rendering="inherit" font-size="12" dy=".8em" y="288.5" x="494.5">tc config</text></g></g><g id="minder_node14"><g id="node_expander10" style="cursor: pointer;" display="none"><path id="kity_path_278" fill="white" stroke="gray" d="M596.5,268.5A6,6,0,1,1,584.5,268.5A6,6,0,1,1,596.5,268.5"></path><path id="kity_path_279" fill="none" stroke="gray"></path></g><path id="node_outline11" fill="none" stroke="none" d="M601.5,257.5h74a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-74a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text11" fill="black"><text id="kity_text_137" text-rendering="inherit" font-size="12" dy=".8em" y="262.5" x="606.5">lock config</text></g></g><g id="minder_node15"><g id="node_expander11" style="cursor: pointer;" display="none"><path id="kity_path_281" fill="white" stroke="gray" d="M596.5,320.5A6,6,0,1,1,584.5,320.5A6,6,0,1,1,596.5,320.5"></path><path id="kity_path_282" fill="none" stroke="gray"></path></g><path id="node_outline12" fill="none" stroke="none" d="M601.5,309.5h78a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-78a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text12" fill="black"><text id="kity_text_139" text-rendering="inherit" font-size="12" dy=".8em" y="314.5" x="606.5">store config</text></g></g><g id="minder_node16"><g id="node_expander16" style="cursor: pointer;"><path id="kity_path_296" fill="white" stroke="gray" d="M387.5,398.5A6,6,0,1,1,375.5,398.5A6,6,0,1,1,387.5,398.5"></path><path id="kity_path_297" fill="none" stroke="gray" d="M377,398.5L386,398.5"></path></g><path id="node_outline17" fill="none" stroke="none" d="M392.5,387.5h76a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-76a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text17" fill="black"><text id="kity_text_149" text-rendering="inherit" font-size="12" dy=".8em" y="392.5" x="397.5">constructor</text></g></g><g id="minder_node17"><g id="node_expander14" style="cursor: pointer;" display="none"><path id="kity_path_290" fill="white" stroke="gray" d="M513.5,372.5A6,6,0,1,1,501.5,372.5A6,6,0,1,1,513.5,372.5"></path><path id="kity_path_291" fill="none" stroke="gray"></path></g><path id="node_outline15" fill="none" stroke="none" d="M518.5,361.5h173a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-173a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text15" fill="black"><text id="kity_text_145" text-rendering="inherit" font-size="12" dy=".8em" y="366.5" x="523.5">branch session lock channel</text></g></g><g id="minder_node18"><g id="node_expander15" style="cursor: pointer;" display="none"><path id="kity_path_293" fill="white" stroke="gray" d="M513.5,424.5A6,6,0,1,1,501.5,424.5A6,6,0,1,1,513.5,424.5"></path><path id="kity_path_294" fill="none" stroke="gray"></path></g><path id="node_outline16" fill="none" stroke="none" d="M518.5,413.5h168a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-168a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text16" fill="black"><text id="kity_text_147" text-rendering="inherit" font-size="12" dy=".8em" y="418.5" x="523.5">global session lock channel</text></g></g><g id="minder_node19"><g id="node_expander22" style="cursor: pointer;"><path id="kity_path_314" fill="white" stroke="gray" d="M387.5,528.5A6,6,0,1,1,375.5,528.5A6,6,0,1,1,387.5,528.5"></path><path id="kity_path_315" fill="none" stroke="gray" d="M377,528.5L386,528.5"></path></g><path id="node_outline23" fill="none" stroke="none" d="M392.5,517.5h34a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-34a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text23" fill="black"><text id="kity_text_161" text-rendering="inherit" font-size="12" dy=".8em" y="522.5" x="397.5">lock</text></g></g><g id="minder_node20"><g id="node_expander21" style="cursor: pointer;"><path id="kity_path_311" fill="white" stroke="gray" d="M471.5,528.5A6,6,0,1,1,459.5,528.5A6,6,0,1,1,471.5,528.5"></path><path id="kity_path_312" fill="none" stroke="gray" d="M461,528.5L470,528.5"></path></g><path id="node_outline22" fill="none" stroke="none" d="M476.5,517.5h63a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-63a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text22" fill="black"><text id="kity_text_159" text-rendering="inherit" font-size="12" dy=".8em" y="522.5" x="481.5">interface</text></g></g><g id="minder_node22"><g id="node_expander18" style="cursor: pointer;" display="none"><path id="kity_path_302" fill="white" stroke="gray" d="M584.5,476.5A6,6,0,1,1,572.5,476.5A6,6,0,1,1,584.5,476.5"></path><path id="kity_path_303" fill="none" stroke="gray"></path></g><path id="node_outline19" fill="none" stroke="rgb(57, 80, 96)" d="M589.5,465.5h85a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-85a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z" stroke-width="3"></path><g id="node_text19" fill="black"><text id="kity_text_153" text-rendering="inherit" font-size="12" dy=".8em" y="470.5" x="594.5">memory lock</text></g></g><g id="minder_node23"><g id="node_expander19" style="cursor: pointer;" display="none"><path id="kity_path_305" fill="white" stroke="gray" d="M584.5,528.5A6,6,0,1,1,572.5,528.5A6,6,0,1,1,584.5,528.5"></path><path id="kity_path_306" fill="none" stroke="gray"></path></g><path id="node_outline20" fill="none" stroke="none" d="M589.5,517.5h65a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-65a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text20" fill="black"><text id="kity_text_155" text-rendering="inherit" font-size="12" dy=".8em" y="522.5" x="594.5">etcd lock</text></g></g><g id="minder_node24"><g id="node_expander20" style="cursor: pointer;" display="none"><path id="kity_path_308" fill="white" stroke="gray" d="M584.5,580.5A6,6,0,1,1,572.5,580.5A6,6,0,1,1,584.5,580.5"></path><path id="kity_path_309" fill="none" stroke="gray"></path></g><path id="node_outline21" fill="none" stroke="none" d="M589.5,569.5h53a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-53a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text21" fill="black"><text id="kity_text_157" text-rendering="inherit" font-size="12" dy=".8em" y="574.5" x="594.5">db lock</text></g></g><g id="minder_node25"><g id="node_expander24" style="cursor: pointer;"><path id="kity_path_320" fill="white" stroke="gray" d="M387.5,632.5A6,6,0,1,1,375.5,632.5A6,6,0,1,1,387.5,632.5"></path><path id="kity_path_321" fill="none" stroke="gray" d="M377,632.5L386,632.5"></path></g><path id="node_outline25" fill="none" stroke="none" d="M392.5,621.5h48a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-48a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text25" fill="black"><text id="kity_text_165" text-rendering="inherit" font-size="12" dy=".8em" y="626.5" x="397.5">model</text></g></g><g id="minder_node26"><g id="node_expander23" style="cursor: pointer;" display="none"><path id="kity_path_317" fill="white" stroke="gray" d="M485.5,632.5A6,6,0,1,1,473.5,632.5A6,6,0,1,1,485.5,632.5"></path><path id="kity_path_318" fill="none" stroke="gray"></path></g><path id="node_outline24" fill="none" stroke="none" d="M490.5,621.5h108a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-108a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text24" fill="black"><text id="kity_text_163" text-rendering="inherit" font-size="12" dy=".8em" y="626.5" x="495.5">session condition</text></g></g><g id="minder_node27"><g id="node_expander33" style="cursor: pointer;"><path id="kity_path_347" fill="white" stroke="gray" d="M387.5,801.5A6,6,0,1,1,375.5,801.5A6,6,0,1,1,387.5,801.5"></path><path id="kity_path_348" fill="none" stroke="gray" d="M377,801.5L386,801.5"></path></g><path id="node_outline34" fill="none" stroke="none" d="M392.5,790.5h47a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-47a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z" stroke-width="3"></path><g id="node_text34" fill="black"><text id="kity_text_183" text-rendering="inherit" font-size="12" dy=".8em" y="795.5" x="397.5">holder</text></g></g><g id="minder_node28"><g id="node_expander29" style="cursor: pointer;"><path id="kity_path_335" fill="white" stroke="gray" d="M484.5,736.5A6,6,0,1,1,472.5,736.5A6,6,0,1,1,484.5,736.5"></path><path id="kity_path_336" fill="none" stroke="gray" d="M474,736.5L483,736.5"></path></g><path id="node_outline30" fill="none" stroke="none" d="M489.5,725.5h63a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-63a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text30" fill="black"><text id="kity_text_175" text-rendering="inherit" font-size="12" dy=".8em" y="730.5" x="494.5">interface</text></g></g><g id="minder_node30"><g id="node_expander26" style="cursor: pointer;" display="none"><path id="kity_path_326" fill="white" stroke="gray" d="M597.5,684.5A6,6,0,1,1,585.5,684.5A6,6,0,1,1,597.5,684.5"></path><path id="kity_path_327" fill="none" stroke="gray"></path></g><path id="node_outline27" fill="none" stroke="none" d="M602.5,673.5h57a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-57a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z" stroke-width="3"></path><g id="node_text27" fill="black"><text id="kity_text_169" text-rendering="inherit" font-size="12" dy=".8em" y="678.5" x="607.5">file store</text></g></g><g id="minder_node31"><g id="node_expander27" style="cursor: pointer;" display="none"><path id="kity_path_329" fill="white" stroke="gray" d="M597.5,736.5A6,6,0,1,1,585.5,736.5A6,6,0,1,1,597.5,736.5"></path><path id="kity_path_330" fill="none" stroke="gray"></path></g><path id="node_outline28" fill="none" stroke="none" d="M602.5,725.5h57a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-57a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text28" fill="black"><text id="kity_text_171" text-rendering="inherit" font-size="12" dy=".8em" y="730.5" x="607.5">db store</text></g></g><g id="minder_node32"><g id="node_expander28" style="cursor: pointer;" display="none"><path id="kity_path_332" fill="white" stroke="gray" d="M597.5,788.5A6,6,0,1,1,585.5,788.5A6,6,0,1,1,597.5,788.5"></path><path id="kity_path_333" fill="none" stroke="gray"></path></g><path id="node_outline29" fill="none" stroke="none" d="M602.5,777.5h69a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-69a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text29" fill="black"><text id="kity_text_173" text-rendering="inherit" font-size="12" dy=".8em" y="782.5" x="607.5">etcd store</text></g></g><g id="minder_node33"><g id="node_expander32" style="cursor: pointer;"><path id="kity_path_344" fill="white" stroke="gray" d="M484.5,866.5A6,6,0,1,1,472.5,866.5A6,6,0,1,1,484.5,866.5"></path><path id="kity_path_345" fill="none" stroke="gray" d="M474,866.5L483,866.5"></path></g><path id="node_outline33" fill="none" stroke="none" d="M489.5,855.5h107a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-107a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text33" fill="black"><text id="kity_text_181" text-rendering="inherit" font-size="12" dy=".8em" y="860.5" x="494.5">session manager</text></g></g><g id="minder_node34"><g id="node_expander30" style="cursor: pointer;" display="none"><path id="kity_path_338" fill="white" stroke="gray" d="M641.5,840.5A6,6,0,1,1,629.5,840.5A6,6,0,1,1,641.5,840.5"></path><path id="kity_path_339" fill="none" stroke="gray"></path></g><path id="node_outline31" fill="none" stroke="none" d="M646.5,829.5h58a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-58a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text31" fill="black"><text id="kity_text_177" text-rendering="inherit" font-size="12" dy=".8em" y="834.5" x="651.5">memory</text></g></g><g id="minder_node35"><g id="node_expander31" style="cursor: pointer;" display="none"><path id="kity_path_341" fill="white" stroke="gray" d="M641.5,892.5A6,6,0,1,1,629.5,892.5A6,6,0,1,1,641.5,892.5"></path><path id="kity_path_342" fill="none" stroke="gray"></path></g><path id="node_outline32" fill="none" stroke="none" d="M646.5,881.5h26a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-26a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text32" fill="black"><text id="kity_text_179" text-rendering="inherit" font-size="12" dy=".8em" y="886.5" x="651.5">db</text></g></g><g id="minder_node36"><g id="node_expander38" style="cursor: pointer;"><path id="kity_path_362" fill="white" stroke="gray" d="M292.5,1005.5A6,6,0,1,1,280.5,1005.5A6,6,0,1,1,292.5,1005.5"></path><path id="kity_path_363" fill="none" stroke="gray" d="M282,1005.5L291,1005.5"></path></g><path id="node_outline39" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,992.5h51a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-51a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text39" fill="black"><text id="kity_text_193" text-rendering="inherit" font-size="14" dy=".8em" y="998.5" x="313.5">rm</text></g></g><g id="minder_node37"><g id="node_expander35" style="cursor: pointer;" display="none"><path id="kity_path_353" fill="white" stroke="gray" d="M390.5,953.5A6,6,0,1,1,378.5,953.5A6,6,0,1,1,390.5,953.5"></path><path id="kity_path_354" fill="none" stroke="gray"></path></g><path id="node_outline36" fill="none" stroke="none" d="M395.5,942.5h47a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-47a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text36" fill="black"><text id="kity_text_187" text-rendering="inherit" font-size="12" dy=".8em" y="947.5" x="400.5">config</text></g></g><g id="minder_node38"><g id="node_expander36" style="cursor: pointer;" display="none"><path id="kity_path_356" fill="white" stroke="gray" d="M390.5,1005.5A6,6,0,1,1,378.5,1005.5A6,6,0,1,1,390.5,1005.5"></path><path id="kity_path_357" fill="none" stroke="gray"></path></g><path id="node_outline37" fill="none" stroke="none" d="M395.5,994.5h64a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-64a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text37" fill="black"><text id="kity_text_189" text-rendering="inherit" font-size="12" dy=".8em" y="999.5" x="400.5">sql parser</text></g></g><g id="minder_node39"><g id="node_expander37" style="cursor: pointer;" display="none"><path id="kity_path_359" fill="white" stroke="gray" d="M390.5,1057.5A6,6,0,1,1,378.5,1057.5A6,6,0,1,1,390.5,1057.5"></path><path id="kity_path_360" fill="none" stroke="gray"></path></g><path id="node_outline38" fill="none" stroke="none" d="M395.5,1046.5h62a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-62a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text38" fill="black"><text id="kity_text_191" text-rendering="inherit" font-size="12" dy=".8em" y="1051.5" x="400.5">undo log</text></g></g><g id="minder_node40"><g id="node_expander39" style="cursor: pointer;" display="none"><path id="kity_path_365" fill="white" stroke="gray" d="M292.5,1121.5A6,6,0,1,1,280.5,1121.5A6,6,0,1,1,292.5,1121.5"></path><path id="kity_path_366" fill="none" stroke="gray"></path></g><path id="node_outline40" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,1108.5h52a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-52a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text40" fill="black"><text id="kity_text_195" text-rendering="inherit" font-size="14" dy=".8em" y="1114.5" x="313.5">tm</text></g></g><g id="minder_node41"><g id="node_expander43" style="cursor: pointer;"><path id="kity_path_377" fill="white" stroke="gray" d="M292.5,1211.5A6,6,0,1,1,280.5,1211.5A6,6,0,1,1,292.5,1211.5"></path><path id="kity_path_378" fill="none" stroke="gray" d="M282,1211.5L291,1211.5"></path></g><path id="node_outline44" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,1198.5h86a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-86a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text44" fill="black"><text id="kity_text_203" text-rendering="inherit" font-size="14" dy=".8em" y="1204.5" x="313.5">logging</text></g></g><g id="minder_node42"><g id="node_expander42" style="cursor: pointer;"><path id="kity_path_374" fill="white" stroke="gray" d="M425.5,1211.5A6,6,0,1,1,413.5,1211.5A6,6,0,1,1,425.5,1211.5"></path><path id="kity_path_375" fill="none" stroke="gray" d="M415,1211.5L424,1211.5"></path></g><path id="node_outline43" fill="none" stroke="none" d="M430.5,1200.5h63a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-63a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text43" fill="black"><text id="kity_text_201" text-rendering="inherit" font-size="12" dy=".8em" y="1205.5" x="435.5">interface</text></g></g><g id="minder_node43"><g id="node_expander40" style="cursor: pointer;" display="none"><path id="kity_path_368" fill="white" stroke="gray" d="M538.5,1185.5A6,6,0,1,1,526.5,1185.5A6,6,0,1,1,538.5,1185.5"></path><path id="kity_path_369" fill="none" stroke="gray"></path></g><path id="node_outline41" fill="none" stroke="none" d="M543.5,1174.5h77a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-77a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text41" fill="black"><text id="kity_text_197" text-rendering="inherit" font-size="12" dy=".8em" y="1179.5" x="548.5">console log</text></g></g><g id="minder_node44"><g id="node_expander41" style="cursor: pointer;" display="none"><path id="kity_path_371" fill="white" stroke="gray" d="M538.5,1237.5A6,6,0,1,1,526.5,1237.5A6,6,0,1,1,538.5,1237.5"></path><path id="kity_path_372" fill="none" stroke="gray"></path></g><path id="node_outline42" fill="none" stroke="none" d="M543.5,1226.5h48a5,5,0,0,1,5,5v12a5,5,0,0,1,-5,5h-48a5,5,0,0,1,-5,-5v-12a5,5,0,0,1,5,-5z"></path><g id="node_text42" fill="black"><text id="kity_text_199" text-rendering="inherit" font-size="12" dy=".8em" y="1231.5" x="548.5">file log</text></g></g><g id="minder_node45"><g id="node_expander44" style="cursor: pointer;" display="none"><path id="kity_path_380" fill="white" stroke="gray" d="M292.5,1301.5A6,6,0,1,1,280.5,1301.5A6,6,0,1,1,292.5,1301.5"></path><path id="kity_path_381" fill="none" stroke="gray"></path></g><path id="node_outline45" fill="rgb(238, 243, 246)" stroke="rgb(115, 161, 191)" d="M296.5,1288.5h53a3,3,0,0,1,3,3v20a3,3,0,0,1,-3,3h-53a3,3,0,0,1,-3,-3v-20a3,3,0,0,1,3,-3z" stroke-width="1"></path><g id="node_text45" fill="black"><text id="kity_text_205" text-rendering="inherit" font-size="14" dy=".8em" y="1294.5" x="313.5">util</text></g></g></g></g></svg>
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..cc1f07c
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,17 @@
+module github.com/dk-lockdown/seata-golang
+
+go 1.13.3
+
+require (
+ github.com/dubbogo/getty v1.3.3
+ github.com/dubbogo/gost v1.6.0
+ github.com/gorilla/websocket v1.4.1 // indirect
+ github.com/pkg/errors v0.9.1
+ github.com/stretchr/testify v1.5.1
+ go.uber.org/atomic v1.5.0
+ go.uber.org/zap v1.14.0 // indirect
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
+ gopkg.in/yaml.v2 v2.2.8
+ vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10
+ vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..ee4f0bc
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,84 @@
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs=
+github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0=
+github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8=
+github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
+github.com/dubbogo/gost v1.6.0 h1:30ERJDdVUcfpc/oFtqPspazQ5dHfyRbDQ2QZvAFxaUE=
+github.com/dubbogo/gost v1.6.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o=
+go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 h1:pxt6fVJP67Hxo1qk8JalUghLlk3abYByl+3e0JYfUlE=
+vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10/go.mod h1:fl9OF22g6MTKgvHA1hqMXe/L7+ULWofVTwbC9loGu7A=
+vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d h1:Mp6WiHHuiwHaknxTdxJ8pvC9/B4pOgW1PamKGexG7Fs=
+vimagination.zapto.org/memio v0.0.0-20200222190306-588ebc67b97d/go.mod h1:zHGDKp2tyvF4IAfLti4pKYqCJucXYmmKMb3UMrCHK/4=
diff --git a/logging/logging.go b/logging/logging.go
new file mode 100644
index 0000000..62ded83
--- /dev/null
+++ b/logging/logging.go
@@ -0,0 +1,186 @@
+package logging
+
+import (
+ "fmt"
+ "log"
+ "os"
+)
+
+// Level represents the level of logging.
+type LogLevel uint8
+
+const (
+ Debug LogLevel = iota
+ Info
+ Warn
+ Error
+ Fatal
+ Panic
+)
+
+type ILogger interface {
+ Debug(v ...interface{})
+ Debugf(format string, v ...interface{})
+
+ Info(v ...interface{})
+ Infof(format string, v ...interface{})
+
+ Warn(v ...interface{})
+ Warnf(format string, v ...interface{})
+
+ Error(v ...interface{})
+ Errorf(format string, v ...interface{})
+
+ Fatal(v ...interface{})
+ Fatalf(format string, v ...interface{})
+
+ Panic(v ...interface{})
+ Panicf(format string, v ...interface{})
+}
+
+const (
+ DefaultLogLevel = Info
+ DefaultNamespace = "default"
+)
+
+type SeataLogger struct {
+ loggers []*log.Logger
+ namespace string
+ logLevel LogLevel
+}
+
+var Logger *SeataLogger
+
+func init() {
+ var loggers = make([]*log.Logger, 0)
+ loggers = append(loggers, log.New(os.Stdout, "", log.LstdFlags))
+ Logger = &SeataLogger{
+ loggers: loggers,
+ namespace: DefaultNamespace,
+ logLevel: DefaultLogLevel,
+ }
+}
+
+func merge(namespace, logLevel, msg string) string {
+ return fmt.Sprintf("%s %s %s", namespace, logLevel, msg)
+}
+
+func SetNamespace(namespace string) {
+ Logger.namespace = namespace
+}
+
+func SetLogLevel(logLevel LogLevel) {
+ Logger.logLevel = logLevel
+}
+
+func AddLogger(logger *log.Logger) {
+ Logger.loggers = append(Logger.loggers, logger)
+}
+
+func (l *SeataLogger) Debug(v ...interface{}) {
+ if Debug < l.logLevel || len(v) == 0 {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "DEBUG", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Debugf(format string, v ...interface{}) {
+ if Debug < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "DEBUG", fmt.Sprintf(format, v...)))
+ }
+}
+
+func (l *SeataLogger) Info(v ...interface{}) {
+ if Info < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "INFO", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Infof(format string, v ...interface{}) {
+ if Info < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "INFO", fmt.Sprintf(format, v...)))
+ }
+}
+
+func (l *SeataLogger) Warn(v ...interface{}) {
+ if Warn < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "WARNING", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Warnf(format string, v ...interface{}) {
+ if Warn < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "WARNING", fmt.Sprintf(format, v...)))
+ }
+}
+
+func (l *SeataLogger) Error(v ...interface{}) {
+ if Error < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "ERROR", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Errorf(format string, v ...interface{}) {
+ if Error < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "ERROR", fmt.Sprintf(format, v...)))
+ }
+}
+
+func (l *SeataLogger) Fatal(v ...interface{}) {
+ if Fatal < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "FATAL", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Fatalf(format string, v ...interface{}) {
+ if Fatal < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "FATAL", fmt.Sprintf(format, v...)))
+ }
+}
+
+func (l *SeataLogger) Panic(v ...interface{}) {
+ if Panic < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "PANIC", fmt.Sprint(v...)))
+ }
+}
+
+func (l *SeataLogger) Panicf(format string, v ...interface{}) {
+ if Panic < l.logLevel {
+ return
+ }
+ for _,log := range l.loggers {
+ log.Print(merge(l.namespace, "PANIC", fmt.Sprintf(format, v...)))
+ }
+}
\ No newline at end of file
diff --git a/logging/logging_test.go b/logging/logging_test.go
new file mode 100644
index 0000000..3a42f3c
--- /dev/null
+++ b/logging/logging_test.go
@@ -0,0 +1,7 @@
+package logging
+
+import "testing"
+
+func TestSeataLogger_Info(t *testing.T) {
+ Logger.Info("there is a bug")
+}
\ No newline at end of file
diff --git a/meta/branch_status.go b/meta/branch_status.go
new file mode 100644
index 0000000..8bb019f
--- /dev/null
+++ b/meta/branch_status.go
@@ -0,0 +1,102 @@
+package meta
+
+import "fmt"
+
+type BranchStatus byte
+
+const (
+ /**
+ * The BranchStatus_Unknown.
+ * description:BranchStatus_Unknown branch status.
+ */
+ BranchStatusUnknown BranchStatus = iota
+
+ /**
+ * The BranchStatus_Registered.
+ * description:BranchStatus_Registered to TC.
+ */
+ BranchStatusRegistered
+
+ /**
+ * The Phase one done.
+ * description:Branch logic is successfully done at phase one.
+ */
+ BranchStatusPhaseoneDone
+
+ /**
+ * The Phase one failed.
+ * description:Branch logic is failed at phase one.
+ */
+ BranchStatusPhaseoneFailed
+
+ /**
+ * The Phase one timeout.
+ * description:Branch logic is NOT reported for a timeout.
+ */
+ BranchStatusPhaseoneTimeout
+
+ /**
+ * The Phase two committed.
+ * description:Commit logic is successfully done at phase two.
+ */
+ BranchStatusPhasetwoCommitted
+
+ /**
+ * The Phase two commit failed retryable.
+ * description:Commit logic is failed but retryable.
+ */
+ BranchStatusPhasetwoCommitFailedRetryable
+
+ /**
+ * The Phase two commit failed unretryable.
+ * description:Commit logic is failed and NOT retryable.
+ */
+ BranchStatusPhasetwoCommitFailedUnretryable
+
+ /**
+ * The Phase two rollbacked.
+ * description:Rollback logic is successfully done at phase two.
+ */
+ BranchStatusPhasetwoRollbacked
+
+ /**
+ * The Phase two rollback failed retryable.
+ * description:Rollback logic is failed but retryable.
+ */
+ BranchStatusPhasetwoRollbackFailedRetryable
+
+ /**
+ * The Phase two rollback failed unretryable.
+ * description:Rollback logic is failed but NOT retryable.
+ */
+ BranchStatusPhasetwoRollbackFailedUnretryable
+)
+
+func (s BranchStatus) String() string {
+ switch s {
+ case BranchStatusUnknown:
+ return "Unknown"
+ case BranchStatusRegistered:
+ return "Registered"
+ case BranchStatusPhaseoneDone:
+ return "PhaseoneDone"
+ case BranchStatusPhaseoneFailed:
+ return "PhaseoneFailed"
+ case BranchStatusPhaseoneTimeout:
+ return "PhaseoneTimeout"
+ case BranchStatusPhasetwoCommitted:
+ return "PhasetwoCommitted"
+ case BranchStatusPhasetwoCommitFailedRetryable:
+ return "PhasetwoCommitFailedRetryable"
+ case BranchStatusPhasetwoCommitFailedUnretryable:
+ return "CommitFailedUnretryable"
+ case BranchStatusPhasetwoRollbacked:
+ return "PhasetwoRollbacked"
+ case BranchStatusPhasetwoRollbackFailedRetryable:
+ return "RollbackFailedRetryable"
+ case BranchStatusPhasetwoRollbackFailedUnretryable:
+ return "RollbackFailedUnretryable"
+ default:
+ return fmt.Sprintf("%d", s)
+ }
+}
\ No newline at end of file
diff --git a/meta/branch_type.go b/meta/branch_type.go
new file mode 100644
index 0000000..ece65b9
--- /dev/null
+++ b/meta/branch_type.go
@@ -0,0 +1,36 @@
+package meta
+
+import "fmt"
+
+type BranchType byte
+
+const (
+ /**
+ * The At.
+ */
+ // BranchType_AT Branch
+ BranchTypeAT BranchType = iota
+
+ /**
+ * The BranchType_TCC.
+ */
+ BranchTypeTCC
+
+ /**
+ * The BranchType_SAGA.
+ */
+ BranchTypeSAGA
+)
+
+func (t BranchType) String() string {
+ switch t {
+ case BranchTypeAT:
+ return "AT"
+ case BranchTypeTCC:
+ return "TCC"
+ case BranchTypeSAGA:
+ return "SAGA"
+ default:
+ return fmt.Sprintf("%d", t)
+ }
+}
\ No newline at end of file
diff --git a/meta/global_status.go b/meta/global_status.go
new file mode 100644
index 0000000..2f129dd
--- /dev/null
+++ b/meta/global_status.go
@@ -0,0 +1,142 @@
+package meta
+
+import "fmt"
+
+type GlobalStatus int32
+
+const (
+ /**
+ * Un known global status.
+ */
+ // BranchStatus_Unknown
+ GlobalStatusUnknown GlobalStatus = iota
+
+ /**
+ * The GlobalStatus_Begin.
+ */
+ // PHASE 1: can accept new branch registering.
+ GlobalStatusBegin
+
+ /**
+ * PHASE 2: Running Status: may be changed any time.
+ */
+ // Committing.
+ GlobalStatusCommitting
+
+ /**
+ * The Commit retrying.
+ */
+ // Retrying commit after a recoverable failure.
+ GlobalStatusCommitRetrying
+
+ /**
+ * Rollbacking global status.
+ */
+ // Rollbacking
+ GlobalStatusRollbacking
+
+ /**
+ * The Rollback retrying.
+ */
+ // Retrying rollback after a recoverable failure.
+ GlobalStatusRollbackRetrying
+
+ /**
+ * The Timeout rollbacking.
+ */
+ // Rollbacking since timeout
+ GlobalStatusTimeoutRollbacking
+
+ /**
+ * The Timeout rollback retrying.
+ */
+ // Retrying rollback (since timeout) after a recoverable failure.
+ GlobalStatusTimeoutRollbackRetrying
+
+ /**
+ * All branches can be async committed. The committing is NOT done yet, but it can be seen as committed for TM/RM
+ * client.
+ */
+ GlobalStatusAsyncCommitting
+
+ /**
+ * PHASE 2: Final Status: will NOT change any more.
+ */
+ // Finally: global transaction is successfully committed.
+ GlobalStatusCommitted
+
+ /**
+ * The Commit failed.
+ */
+ // Finally: failed to commit
+ GlobalStatusCommitFailed
+
+ /**
+ * The Rollbacked.
+ */
+ // Finally: global transaction is successfully rollbacked.
+ GlobalStatusRollbacked
+
+ /**
+ * The Rollback failed.
+ */
+ // Finally: failed to rollback
+ GlobalStatusRollbackFailed
+
+ /**
+ * The Timeout rollbacked.
+ */
+ // Finally: global transaction is successfully rollbacked since timeout.
+ GlobalStatusTimeoutRollbacked
+
+ /**
+ * The Timeout rollback failed.
+ */
+ // Finally: failed to rollback since timeout
+ GlobalStatusTimeoutRollbackFailed
+
+ /**
+ * The Finished.
+ */
+ // Not managed in session MAP any more
+ GlobalStatusFinished
+)
+
+func (s GlobalStatus) String() string {
+ switch s {
+ case GlobalStatusUnknown:
+ return "Unknown"
+ case GlobalStatusBegin:
+ return "Begin"
+ case GlobalStatusCommitting:
+ return "Committing"
+ case GlobalStatusCommitRetrying:
+ return "CommitRetrying"
+ case GlobalStatusRollbacking:
+ return "Rollbacking"
+ case GlobalStatusRollbackRetrying:
+ return "RollbackRetrying"
+ case GlobalStatusTimeoutRollbacking:
+ return "TimeoutRollbacking"
+ case GlobalStatusTimeoutRollbackRetrying:
+ return "TimeoutRollbackRetrying"
+ case GlobalStatusAsyncCommitting:
+ return "AsyncCommitting"
+ case GlobalStatusCommitted:
+ return "Committed"
+ case GlobalStatusCommitFailed:
+ return "CommitFailed"
+ case GlobalStatusRollbacked:
+ return "Rollbacked"
+ case GlobalStatusRollbackFailed:
+ return "RollbackFailed"
+ case GlobalStatusTimeoutRollbacked:
+ return "TimeoutRollbacked"
+ case GlobalStatusTimeoutRollbackFailed:
+ return "TimeoutRollbackFailed"
+ case GlobalStatusFinished:
+ return "Finished"
+ default:
+ return fmt.Sprintf("%d", s)
+ }
+}
\ No newline at end of file
diff --git a/meta/transaction_exception_code.go b/meta/transaction_exception_code.go
new file mode 100644
index 0000000..a8d6ddb
--- /dev/null
+++ b/meta/transaction_exception_code.go
@@ -0,0 +1,110 @@
+package meta
+
+type TransactionExceptionCode byte
+
+const (
+ /**
+ * Unknown transaction exception code.
+ */
+ TransactionExceptionCodeUnknown TransactionExceptionCode = iota
+
+ /**
+ * BeginFailed
+ */
+ TransactionExceptionCodeBeginFailed
+
+ /**
+ * Lock key conflict transaction exception code.
+ */
+ TransactionExceptionCodeLockKeyConflict
+
+ /**
+ * Io transaction exception code.
+ */
+ IO
+
+ /**
+ * Branch rollback failed retriable transaction exception code.
+ */
+ TransactionExceptionCodeBranchRollbackFailedRetriable
+
+ /**
+ * Branch rollback failed unretriable transaction exception code.
+ */
+ TransactionExceptionCodeBranchRollbackFailedUnretriable
+
+ /**
+ * Branch register failed transaction exception code.
+ */
+ TransactionExceptionCodeBranchRegisterFailed
+
+ /**
+ * Branch report failed transaction exception code.
+ */
+ TransactionExceptionCodeBranchReportFailed
+
+ /**
+ * Lockable check failed transaction exception code.
+ */
+ TransactionExceptionCodeLockableCheckFailed
+
+ /**
+ * Branch transaction not exist transaction exception code.
+ */
+ TransactionExceptionCodeBranchTransactionNotExist
+
+ /**
+ * Global transaction not exist transaction exception code.
+ */
+ TransactionExceptionCodeGlobalTransactionNotExist
+
+ /**
+ * Global transaction not active transaction exception code.
+ */
+ TransactionExceptionCodeGlobalTransactionNotActive
+
+ /**
+ * Global transaction status invalid transaction exception code.
+ */
+ TransactionExceptionCodeGlobalTransactionStatusInvalid
+
+ /**
+ * Failed to send branch commit request transaction exception code.
+ */
+ TransactionExceptionCodeFailedToSendBranchCommitRequest
+
+ /**
+ * Failed to send branch rollback request transaction exception code.
+ */
+ TransactionExceptionCodeFailedToSendBranchRollbackRequest
+
+ /**
+ * Failed to add branch transaction exception code.
+ */
+ TransactionExceptionCodeFailedToAddBranch
+
+ /**
+ * Failed to lock global transaction exception code.
+ */
+ TransactionExceptionCodeFailedLockGlobalTranscation
+
+ /**
+ * FailedWriteSession
+ */
+ TransactionExceptionCodeFailedWriteSession
+
+ /**
+ * Failed to holder exception code
+ */
+ FailedStore
+)
+
+type TransactionException struct {
+ Code TransactionExceptionCode
+ Message string
+}
+
+//Error 隐式继承 builtin.error 接口
+func (e TransactionException) Error() string {
+ return "TransactionException: " + e.Message
+}
\ No newline at end of file
diff --git a/meta/transaction_role.go b/meta/transaction_role.go
new file mode 100644
index 0000000..767633f
--- /dev/null
+++ b/meta/transaction_role.go
@@ -0,0 +1,33 @@
+package meta
+
+import "fmt"
+
+type TransactionRole byte
+
+const (
+ /**
+ * tm
+ */
+ TMROLE TransactionRole = iota
+ /**
+ * rm
+ */
+ RMROLE
+ /**
+ * server
+ */
+ SERVERROLE
+)
+
+func (r TransactionRole) String() string {
+ switch r {
+ case TMROLE:
+ return "TMROLE"
+ case RMROLE:
+ return "RMROLE"
+ case SERVERROLE:
+ return "SERVERROLE"
+ default:
+ return fmt.Sprintf("%d", r)
+ }
+}
\ No newline at end of file
diff --git a/model/resource.go b/model/resource.go
new file mode 100644
index 0000000..6a91e60
--- /dev/null
+++ b/model/resource.go
@@ -0,0 +1,28 @@
+package model
+
+import "github.com/dk-lockdown/seata-golang/meta"
+
+type IResource interface {
+ /**
+ * Get the resource group id.
+ * e.g. master and slave data-source should be with the same resource group id.
+ *
+ * @return resource group id.
+ */
+ getResourceGroupId() string
+
+ /**
+ * Get the resource id.
+ * e.g. url of a data-source could be the id of the db data-source resource.
+ *
+ * @return resource id.
+ */
+ getResourceId() string
+
+ /**
+ * get resource type, BranchType_AT, BranchType_TCC, BranchType_SAGA and XA
+ *
+ * @return
+ */
+ getBranchType() meta.BranchType
+}
\ No newline at end of file
diff --git a/model/set.go b/model/set.go
new file mode 100644
index 0000000..7b7fd1e
--- /dev/null
+++ b/model/set.go
@@ -0,0 +1,69 @@
+package model
+
+import "sync"
+
+type Set struct {
+ m map[string]bool
+ sync.RWMutex
+}
+
+func NewSet() *Set {
+ return &Set{
+ m: make(map[string]bool),
+ }
+}
+
+// Add add
+func (s *Set) Add(item string) {
+ s.Lock()
+ defer s.Unlock()
+ s.m[item] = true
+}
+
+// Remove deletes the specified item from the map
+func (s *Set) Remove(item string) {
+ s.Lock()
+ defer s.Unlock()
+ delete(s.m, item)
+}
+
+// Has looks for the existence of an item
+func (s *Set) Has(item string) bool {
+ s.RLock()
+ defer s.RUnlock()
+ _, ok := s.m[item]
+ return ok
+}
+
+// Len returns the number of items in a set.
+func (s *Set) Len() int {
+ return len(s.List())
+}
+
+// Clear removes all items from the set
+func (s *Set) Clear() {
+ s.Lock()
+ defer s.Unlock()
+ s.m = make(map[string]bool)
+}
+
+// IsEmpty checks for emptiness
+func (s *Set) IsEmpty() bool {
+ if s.Len() == 0 {
+ return true
+ }
+ return false
+}
+
+// Set returns a slice of all items
+func (s *Set) List() []string {
+ s.RLock()
+ defer s.RUnlock()
+ list := make([]string, 0)
+ for item := range s.m {
+ list = append(list, item)
+ }
+ return list
+}
+
+
diff --git a/protocal/codec/codec.go b/protocal/codec/codec.go
new file mode 100644
index 0000000..0366cad
--- /dev/null
+++ b/protocal/codec/codec.go
@@ -0,0 +1,243 @@
+package codec
+
+import (
+ "bytes"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "vimagination.zapto.org/byteio"
+)
+
+type SerializerType byte
+
+const (
+ SEATA = byte(0x1)
+ PROTOBUF = byte(0x2)
+ KRYO = byte(0x4)
+ FST = byte(0x8)
+)
+
+type Encoder func(in interface{}) []byte
+
+type Decoder func(in []byte) (interface{},int)
+
+func MessageEncoder(codecType byte,in interface{}) []byte {
+ switch codecType {
+ case SEATA:
+ return SeataEncoder(in)
+ default:
+ logging.Logger.Errorf("not support codecType, %s",codecType)
+ return nil
+ }
+}
+
+func MessageDecoder(codecType byte,in []byte) (interface{},int) {
+ switch codecType {
+ case SEATA:
+ return SeataDecoder(in)
+ default:
+ logging.Logger.Errorf("not support codecType, %s",codecType)
+ return nil,0
+ }
+}
+
+
+func SeataEncoder(in interface{}) []byte {
+ var result = make([]byte, 0)
+ msg := in.(protocal.MessageTypeAware)
+ typeCode := msg.GetTypeCode()
+ encoder := getMessageEncoder(typeCode)
+
+ typeC := uint16(typeCode)
+ if encoder != nil {
+ body := encoder(in)
+ result = append(result,[]byte{ byte(typeC >> 8), byte(typeC) }...)
+ result = append(result,body...)
+ }
+ return result
+}
+
+func SeataDecoder(in []byte) (interface{},int) {
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ typeCode, _ ,_ := r.ReadInt16()
+
+ decoder := getMessageDecoder(typeCode)
+ if decoder != nil {
+ return decoder(in[2:])
+ }
+ return nil,0
+}
+
+func getMessageEncoder(typeCode int16) Encoder {
+ switch typeCode {
+ case protocal.TypeSeataMerge:
+ return MergedWarpMessageEncoder
+ case protocal.TypeSeataMergeResult:
+ return MergeResultMessageEncoder
+ case protocal.TypeRegClt:
+ return RegisterTMRequestEncoder
+ case protocal.TypeRegCltResult:
+ return RegisterTMResponseEncoder
+ case protocal.TypeRegRm:
+ return RegisterRMRequestEncoder
+ case protocal.TypeRegRmResult:
+ return RegisterRMResponseEncoder
+ case protocal.TypeBranchCommit:
+ return BranchCommitRequestEncoder
+ case protocal.TypeBranchRollback:
+ return BranchRollbackRequestEncoder
+ case protocal.TypeGlobalReport:
+ return GlobalReportRequestEncoder
+ default:
+ var encoder Encoder
+ encoder = getMergeRequestMessageEncoder(typeCode)
+ if encoder != nil {
+ return encoder
+ }
+ encoder = getMergeResponseMessageEncoder(typeCode)
+ if encoder != nil {
+ return encoder
+ }
+ logging.Logger.Errorf("not support typeCode, %d",typeCode)
+ return nil
+ }
+}
+
+func getMergeRequestMessageEncoder(typeCode int16) Encoder {
+ switch typeCode {
+ case protocal.TypeGlobalBegin:
+ return GlobalBeginRequestEncoder
+ case protocal.TypeGlobalCommit:
+ return GlobalCommitRequestEncoder
+ case protocal.TypeGlobalRollback:
+ return GlobalRollbackRequestEncoder
+ case protocal.TypeGlobalStatus:
+ return GlobalStatusRequestEncoder
+ case protocal.TypeGlobalLockQuery:
+ return GlobalLockQueryRequestEncoder
+ case protocal.TypeBranchRegister:
+ return BranchRegisterRequestEncoder
+ case protocal.TypeBranchStatusReport:
+ return BranchReportRequestEncoder
+ case protocal.TypeGlobalReport:
+ return GlobalReportRequestEncoder
+ default:
+ break
+ }
+ return nil
+}
+
+func getMergeResponseMessageEncoder(typeCode int16) Encoder {
+ switch typeCode {
+ case protocal.TypeGlobalBeginResult:
+ return GlobalBeginResponseEncoder
+ case protocal.TypeGlobalCommitResult:
+ return GlobalCommitResponseEncoder
+ case protocal.TypeGlobalRollbackResult:
+ return GlobalRollbackResponseEncoder
+ case protocal.TypeGlobalStatusResult:
+ return GlobalStatusResponseEncoder
+ case protocal.TypeGlobalLockQueryResult:
+ return GlobalLockQueryResponseEncoder
+ case protocal.TypeBranchRegisterResult:
+ return BranchRegisterResponseEncoder
+ case protocal.TypeBranchStatusReportResult:
+ return BranchReportResponseEncoder
+ case protocal.TypeBranchCommitResult:
+ return BranchCommitResponseEncoder
+ case protocal.TypeBranchRollbackResult:
+ return BranchRollbackResponseEncoder
+ case protocal.TypeGlobalReportResult:
+ return GlobalReportResponseEncoder
+ default:
+ break
+ }
+ return nil
+}
+
+
+func getMessageDecoder(typeCode int16) Decoder {
+ switch typeCode {
+ case protocal.TypeSeataMerge:
+ return MergedWarpMessageDecoder
+ case protocal.TypeSeataMergeResult:
+ return MergeResultMessageDecoder
+ case protocal.TypeRegClt:
+ return RegisterTMRequestDecoder
+ case protocal.TypeRegCltResult:
+ return RegisterTMResponseDecoder
+ case protocal.TypeRegRm:
+ return RegisterRMRequestDecoder
+ case protocal.TypeRegRmResult:
+ return RegisterRMResponseDecoder
+ case protocal.TypeBranchCommit:
+ return BranchCommitRequestDecoder
+ case protocal.TypeBranchRollback:
+ return BranchRollbackRequestDecoder
+ case protocal.TypeGlobalReport:
+ return GlobalReportRequestDecoder
+ default:
+ var Decoder Decoder
+ Decoder = getMergeRequestMessageDecoder(typeCode)
+ if Decoder != nil {
+ return Decoder
+ }
+ Decoder = getMergeResponseMessageDecoder(typeCode)
+ if Decoder != nil {
+ return Decoder
+ }
+ logging.Logger.Errorf("not support typeCode, %d",typeCode)
+ return nil
+ }
+}
+
+func getMergeRequestMessageDecoder(typeCode int16) Decoder {
+ switch typeCode {
+ case protocal.TypeGlobalBegin:
+ return GlobalBeginRequestDecoder
+ case protocal.TypeGlobalCommit:
+ return GlobalCommitRequestDecoder
+ case protocal.TypeGlobalRollback:
+ return GlobalRollbackRequestDecoder
+ case protocal.TypeGlobalStatus:
+ return GlobalStatusRequestDecoder
+ case protocal.TypeGlobalLockQuery:
+ return GlobalLockQueryRequestDecoder
+ case protocal.TypeBranchRegister:
+ return BranchRegisterRequestDecoder
+ case protocal.TypeBranchStatusReport:
+ return BranchReportRequestDecoder
+ case protocal.TypeGlobalReport:
+ return GlobalReportRequestDecoder
+ default:
+ break
+ }
+ return nil
+}
+
+func getMergeResponseMessageDecoder(typeCode int16) Decoder {
+ switch typeCode {
+ case protocal.TypeGlobalBeginResult:
+ return GlobalBeginResponseDecoder
+ case protocal.TypeGlobalCommitResult:
+ return GlobalCommitResponseDecoder
+ case protocal.TypeGlobalRollbackResult:
+ return GlobalRollbackResponseDecoder
+ case protocal.TypeGlobalStatusResult:
+ return GlobalStatusResponseDecoder
+ case protocal.TypeGlobalLockQueryResult:
+ return GlobalLockQueryResponseDecoder
+ case protocal.TypeBranchRegisterResult:
+ return BranchRegisterResponseDecoder
+ case protocal.TypeBranchStatusReportResult:
+ return BranchReportResponseDecoder
+ case protocal.TypeBranchCommitResult:
+ return BranchCommitResponseDecoder
+ case protocal.TypeBranchRollbackResult:
+ return BranchRollbackResponseDecoder
+ case protocal.TypeGlobalReportResult:
+ return GlobalReportResponseDecoder
+ default:
+ break
+ }
+ return nil
+}
\ No newline at end of file
diff --git a/protocal/codec/seata_decoder.go b/protocal/codec/seata_decoder.go
new file mode 100644
index 0000000..fbd388c
--- /dev/null
+++ b/protocal/codec/seata_decoder.go
@@ -0,0 +1,773 @@
+package codec
+
+import (
+ "bytes"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "vimagination.zapto.org/byteio"
+)
+
+func AbstractResultMessageDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractResultMessage{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ totalReadN += 1
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ return msg,totalReadN
+}
+
+func MergedWarpMessageDecoder(in []byte) (interface{},int) {
+ var (
+ size16 int16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ result := protocal.MergedWarpMessage{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ r.ReadInt32()
+ totalReadN += 4
+ size16,readN,_ = r.ReadInt16()
+ totalReadN += readN
+ result.Msgs = make([]protocal.MessageTypeAware,0)
+ for index := 0; index < int(size16); index++ {
+ typeCode,_,_ := r.ReadInt16()
+ totalReadN += 2
+ decoder := getMessageDecoder(typeCode)
+ if decoder != nil {
+ msg,readN := decoder(in[totalReadN:])
+ totalReadN += readN
+ result.Msgs = append(result.Msgs,msg.(protocal.MessageTypeAware))
+ }
+ }
+ return result,totalReadN
+}
+
+func MergeResultMessageDecoder(in []byte) (interface{},int) {
+ var (
+ size16 int16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ result := protocal.MergeResultMessage{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ r.ReadInt32()
+ totalReadN += 4
+ size16,readN,_ = r.ReadInt16()
+ totalReadN += readN
+ result.Msgs = make([]protocal.MessageTypeAware,0)
+
+ for index := 0; index < int(size16); index++ {
+ typeCode,_,_ := r.ReadInt16()
+ totalReadN += 2
+ decoder := getMessageDecoder(typeCode)
+ if decoder != nil {
+ msg,readN := decoder(in[totalReadN:])
+ totalReadN += readN
+ result.Msgs = append(result.Msgs,msg.(protocal.MessageTypeAware))
+ }
+ }
+ return result,totalReadN
+}
+
+func AbstractIdentifyRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractIdentifyRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Version, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ApplicationId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.TransactionServiceGroup, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ExtraData = make([]byte,int(length16))
+ readN, _ := r.Read(msg.ExtraData)
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func AbstractIdentifyResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractIdentifyResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ identified, _ := r.ReadByte()
+ totalReadN += 1
+ if identified == byte(1){
+ msg.Identified = true
+ } else if identified == byte(0) {
+ msg.Identified = false
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Version, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func RegisterRMRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length32 uint32 = 0
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.RegisterRMRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Version, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ApplicationId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.TransactionServiceGroup, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ExtraData = make([]byte,int(length16))
+ readN, _ := r.Read(msg.ExtraData)
+ totalReadN += readN
+ }
+
+ length32, readN, _ = r.ReadUint32()
+ totalReadN += readN
+ if length32 > 0 {
+ msg.ResourceIds, readN, _ = r.ReadString(int(length32))
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func RegisterRMResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractIdentifyResponseDecoder(in)
+ abstractIdentifyResponse := resp.(protocal.AbstractIdentifyResponse)
+ msg := protocal.RegisterRMResponse{AbstractIdentifyResponse:abstractIdentifyResponse}
+ return msg,totalReadN
+}
+
+func RegisterTMRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractIdentifyRequestDecoder(in)
+ abstractIdentifyRequest := req.(protocal.AbstractIdentifyRequest)
+ msg := protocal.RegisterTMRequest{AbstractIdentifyRequest:abstractIdentifyRequest}
+ return msg,totalReadN
+}
+
+func RegisterTMResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractIdentifyResponseDecoder(in)
+ abstractIdentifyResponse := resp.(protocal.AbstractIdentifyResponse)
+ msg := protocal.RegisterRMResponse{AbstractIdentifyResponse:abstractIdentifyResponse}
+ return msg,totalReadN
+}
+
+func AbstractTransactionResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractTransactionResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ return msg,totalReadN
+}
+
+func AbstractBranchEndRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractBranchEndRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ msg.BranchId, _, _ = r.ReadInt64()
+ totalReadN += 8
+ branchType, _ := r.ReadByte()
+ totalReadN += 1
+ msg.BranchType = meta.BranchType(branchType)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ResourceId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ApplicationData = make([]byte,int(length16))
+ readN,_ := r.Read(msg.ApplicationData)
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func AbstractBranchEndResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractBranchEndResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ msg.BranchId, _, _ = r.ReadInt64()
+ totalReadN += 8
+ branchStatus,_ := r.ReadByte()
+ totalReadN += 1
+ msg.BranchStatus = meta.BranchStatus(branchStatus)
+
+ return msg,totalReadN
+}
+
+func AbstractGlobalEndRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractGlobalEndRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ExtraData = make([]byte,int(length16))
+ readN,_ := r.Read(msg.ExtraData)
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func AbstractGlobalEndResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.AbstractGlobalEndResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ globalStatus,_ := r.ReadByte()
+ totalReadN += 1
+ msg.GlobalStatus = meta.GlobalStatus(globalStatus)
+
+ return msg,totalReadN
+}
+
+func BranchCommitRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractBranchEndRequestDecoder(in)
+ abstractBranchEndRequest := req.(protocal.AbstractBranchEndRequest)
+ msg := protocal.BranchCommitRequest{AbstractBranchEndRequest:abstractBranchEndRequest}
+ return msg,totalReadN
+}
+
+func BranchCommitResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractBranchEndResponseDecoder(in)
+ abstractBranchEndResponse := resp.(protocal.AbstractBranchEndResponse)
+ msg := protocal.BranchCommitResponse{AbstractBranchEndResponse:abstractBranchEndResponse}
+ return msg,totalReadN
+}
+
+func BranchRegisterRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length32 uint32 = 0
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.BranchRegisterRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ branchType, _ := r.ReadByte()
+ totalReadN += 1
+ msg.BranchType = meta.BranchType(branchType)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ResourceId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length32, readN, _ = r.ReadUint32()
+ totalReadN += readN
+ if length32 > 0 {
+ msg.LockKey, readN, _ = r.ReadString(int(length32))
+ totalReadN += readN
+ }
+
+ length32, readN, _ = r.ReadUint32()
+ totalReadN += readN
+ if length32 > 0 {
+ msg.ApplicationData = make([]byte,int(length32))
+ readN,_ := r.Read(msg.ApplicationData)
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func BranchRegisterResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.BranchRegisterResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ msg.BranchId, readN, _ = r.ReadInt64()
+ totalReadN += readN
+
+ return msg,totalReadN
+}
+
+func BranchReportRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.BranchReportRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ msg.BranchId, _, _ = r.ReadInt64()
+ branchStatus, _ := r.ReadByte()
+ msg.Status = meta.BranchStatus(branchStatus)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ResourceId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ApplicationData = make([]byte,int(length16))
+ readN,_ := r.Read(msg.ApplicationData)
+ totalReadN += readN
+ }
+
+ branchType, _ := r.ReadByte()
+ totalReadN += 1
+ msg.BranchType = meta.BranchType(branchType)
+
+ return msg,totalReadN
+}
+
+func BranchReportResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractTransactionResponseDecoder(in)
+ abstractTransactionResponse := resp.(protocal.AbstractTransactionResponse)
+ msg := protocal.BranchReportResponse{AbstractTransactionResponse: abstractTransactionResponse}
+ return msg,totalReadN
+}
+
+func BranchRollbackRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractBranchEndRequestDecoder(in)
+ abstractBranchEndRequest := req.(protocal.AbstractBranchEndRequest)
+ msg := protocal.BranchRollbackRequest{AbstractBranchEndRequest:abstractBranchEndRequest}
+ return msg,totalReadN
+}
+
+func BranchRollbackResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractBranchEndResponseDecoder(in)
+ abstractBranchEndResponse := resp.(protocal.AbstractBranchEndResponse)
+ msg := protocal.BranchRollbackResponse{AbstractBranchEndResponse:abstractBranchEndResponse}
+ return msg,totalReadN
+}
+
+func GlobalBeginRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.GlobalBeginRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ timeout, readN, _ := r.ReadInt32()
+ totalReadN += readN
+ msg.Timeout = timeout
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.TransactionName, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func GlobalBeginResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.GlobalBeginResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ExtraData = make([]byte,int(length16))
+ readN,_ := r.Read(msg.ExtraData)
+ totalReadN += readN
+ }
+
+ return msg,totalReadN
+}
+
+func GlobalCommitRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractGlobalEndRequestDecoder(in)
+ abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest)
+ msg := protocal.GlobalCommitRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest}
+ return msg,totalReadN
+}
+
+func GlobalCommitResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractGlobalEndResponseDecoder(in)
+ abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse)
+ msg := protocal.GlobalCommitResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse}
+ return msg,totalReadN
+}
+
+func GlobalLockQueryRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := BranchRegisterRequestDecoder(in)
+ branchRegisterRequest := req.(protocal.BranchRegisterRequest)
+ msg := protocal.GlobalLockQueryRequest{BranchRegisterRequest:branchRegisterRequest}
+ return msg,totalReadN
+}
+
+func GlobalLockQueryResponseDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.GlobalLockQueryResponse{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ resultCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.ResultCode = protocal.ResultCode(resultCode)
+ if msg.ResultCode == protocal.ResultCodeFailed {
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Msg, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+ }
+
+ exceptionCode, _ := r.ReadByte()
+ totalReadN += 1
+ msg.TransactionExceptionCode = meta.TransactionExceptionCode(exceptionCode)
+
+ lockable, readN, _ := r.ReadUint16()
+ totalReadN += readN
+ if lockable == uint16(1) {
+ msg.Lockable = true
+ } else if lockable == uint16(0) {
+ msg.Lockable = false
+ }
+
+ return msg,totalReadN
+}
+
+func GlobalReportRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.GlobalReportRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.Xid, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ExtraData = make([]byte,int(length16))
+ readN, _ := r.Read(msg.ExtraData)
+ totalReadN += readN
+ }
+
+ globalStatus,_ := r.ReadByte()
+ totalReadN += 1
+ msg.GlobalStatus = meta.GlobalStatus(globalStatus)
+
+ return msg,totalReadN
+}
+
+func GlobalReportResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractGlobalEndResponseDecoder(in)
+ abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse)
+ msg := protocal.GlobalReportResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse}
+ return msg,totalReadN
+}
+
+func GlobalRollbackRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractGlobalEndRequestDecoder(in)
+ abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest)
+ msg := protocal.GlobalRollbackRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest}
+ return msg,totalReadN
+}
+
+func GlobalRollbackResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractGlobalEndResponseDecoder(in)
+ abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse)
+ msg := protocal.GlobalRollbackResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse}
+ return msg,totalReadN
+}
+
+func GlobalStatusRequestDecoder(in []byte) (interface{},int) {
+ req,totalReadN := AbstractGlobalEndRequestDecoder(in)
+ abstractGlobalEndRequest := req.(protocal.AbstractGlobalEndRequest)
+ msg := protocal.GlobalStatusRequest{AbstractGlobalEndRequest:abstractGlobalEndRequest}
+ return msg,totalReadN
+}
+
+func GlobalStatusResponseDecoder(in []byte) (interface{},int) {
+ resp,totalReadN := AbstractGlobalEndResponseDecoder(in)
+ abstractGlobalEndResponse := resp.(protocal.AbstractGlobalEndResponse)
+ msg := protocal.GlobalStatusResponse{AbstractGlobalEndResponse:abstractGlobalEndResponse}
+ return msg,totalReadN
+}
+
+func UndoLogDeleteRequestDecoder(in []byte) (interface{},int) {
+ var (
+ length16 uint16 = 0
+ readN = 0
+ totalReadN = 0
+ )
+ msg := protocal.UndoLogDeleteRequest{}
+
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(in)}
+ branchType, _ := r.ReadByte()
+ totalReadN += 1
+ msg.BranchType = meta.BranchType(branchType)
+
+ length16, readN, _ = r.ReadUint16()
+ totalReadN += readN
+ if length16 > 0 {
+ msg.ResourceId, readN, _ = r.ReadString(int(length16))
+ totalReadN += readN
+ }
+
+ msg.SaveDays, readN, _ = r.ReadInt16()
+ totalReadN += readN
+
+ return msg,totalReadN
+}
\ No newline at end of file
diff --git a/protocal/codec/seata_encoder.go b/protocal/codec/seata_encoder.go
new file mode 100644
index 0000000..1163ec0
--- /dev/null
+++ b/protocal/codec/seata_encoder.go
@@ -0,0 +1,560 @@
+package codec
+
+import (
+ "bytes"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "vimagination.zapto.org/byteio"
+)
+
+func AbstractResultMessageEncoder(in interface{}) []byte {
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ message := in.(protocal.AbstractResultMessage)
+
+ w.WriteByte(byte(message.ResultCode))
+ if message.ResultCode == protocal.ResultCodeFailed {
+ var msg string
+ if message.Msg != "" {
+ if len(message.Msg) > 128 {
+ msg = message.Msg[:128]
+ } else {
+ msg = message.Msg
+ }
+ // 暂时不考虑 message.Msg 包含中文的情况,这样字符串的长度就是 byte 数组的长度
+
+ w.WriteInt16(int16(len(msg)))
+ w.WriteString(msg)
+ } else {
+ w.WriteInt16(zero16)
+ }
+ }
+
+
+ return b.Bytes()
+}
+
+func MergedWarpMessageEncoder(in interface{}) []byte {
+ var (
+ b bytes.Buffer
+ result = make([]byte,0)
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.MergedWarpMessage)
+ w.WriteInt16(int16(len(req.Msgs)))
+
+ for _,msg := range req.Msgs {
+ encoder := getMessageEncoder(msg.GetTypeCode())
+ if encoder != nil {
+ data := encoder(msg)
+ w.WriteInt16(msg.GetTypeCode())
+ w.Write(data)
+ }
+ }
+
+ size := uint32(b.Len())
+ result = append(result,[]byte{ byte(size>>24),byte(size>>16),byte(size>>8),byte(size) }...)
+ result = append(result, b.Bytes()...)
+
+ if len(req.Msgs)>20 {
+ logging.Logger.Debugf("msg in one packet: %s ,buffer size: %s", len(req.Msgs),size)
+ }
+ return result
+}
+
+func MergeResultMessageEncoder(in interface{}) []byte {
+ var (
+ b bytes.Buffer
+ result = make([]byte,0)
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.MergeResultMessage)
+ w.WriteInt16(int16(len(req.Msgs)))
+
+ for _,msg := range req.Msgs {
+ encoder := getMessageEncoder(msg.GetTypeCode())
+ if encoder != nil {
+ data := encoder(msg)
+ w.WriteInt16(msg.GetTypeCode())
+ w.Write(data)
+ }
+ }
+
+ size := uint32(b.Len())
+ result = append(result,[]byte{ byte(size>>24),byte(size>>16),byte(size>>8),byte(size) }...)
+ result = append(result, b.Bytes()...)
+
+ if len(req.Msgs)>20 {
+ logging.Logger.Debugf("msg in one packet: %s ,buffer size: %s", len(req.Msgs),size)
+ }
+ return result
+}
+
+func AbstractIdentifyRequestEncoder(in interface{}) []byte {
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req := in.(protocal.AbstractIdentifyRequest)
+
+ if req.Version != "" {
+ w.WriteInt16(int16(len(req.Version)))
+ w.WriteString(req.Version)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.ApplicationId != "" {
+ w.WriteInt16(int16(len(req.ApplicationId)))
+ w.WriteString(req.ApplicationId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.TransactionServiceGroup != "" {
+ w.WriteInt16(int16(len(req.TransactionServiceGroup)))
+ w.WriteString(req.TransactionServiceGroup)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.ExtraData != nil {
+ w.WriteUint16(uint16(len(req.ExtraData)))
+ w.Write(req.ExtraData)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ return b.Bytes()
+}
+
+func AbstractIdentifyResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.AbstractIdentifyResponse)
+
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ if resp.Identified {
+ w.WriteByte(byte(1))
+ } else {
+ w.WriteByte(byte(0))
+ }
+
+ if resp.Version != "" {
+ w.WriteInt16(int16(len(resp.Version)))
+ w.WriteString(resp.Version)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ return b.Bytes()
+}
+
+func RegisterRMRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.RegisterRMRequest)
+ data := AbstractIdentifyRequestEncoder(req.AbstractIdentifyRequest)
+
+ var (
+ zero32 int32 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ if req.ResourceIds != "" {
+ w.WriteInt32(int32(len(req.ResourceIds)))
+ w.WriteString(req.ResourceIds)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ result := append(data,b.Bytes()...)
+ return result
+}
+
+func RegisterRMResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.RegisterRMResponse)
+ return AbstractIdentifyResponseEncoder(resp.AbstractIdentifyResponse)
+}
+
+func RegisterTMRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.RegisterTMRequest)
+ return AbstractIdentifyRequestEncoder(req.AbstractIdentifyRequest)
+}
+
+func RegisterTMResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.RegisterTMResponse)
+ return AbstractIdentifyResponseEncoder(resp.AbstractIdentifyResponse)
+}
+
+func AbstractTransactionResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.AbstractTransactionResponse)
+ data := AbstractResultMessageEncoder(resp.AbstractResultMessage)
+
+ result := append(data,byte(resp.TransactionExceptionCode))
+
+ return result
+}
+
+func AbstractBranchEndRequestEncoder(in interface{}) []byte {
+ var (
+ zero32 int32 = 0
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.AbstractBranchEndRequest)
+
+ if req.Xid != "" {
+ w.WriteInt16(int16(len(req.Xid)))
+ w.WriteString(req.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ w.WriteInt64(req.BranchId)
+ w.WriteByte(byte(req.BranchType))
+
+ if req.ResourceId != "" {
+ w.WriteInt16(int16(len(req.ResourceId)))
+ w.WriteString(req.ResourceId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.ApplicationData != nil {
+ w.WriteUint32(uint32(len(req.ApplicationData)))
+ w.Write(req.ApplicationData)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ return b.Bytes()
+}
+
+func AbstractBranchEndResponseEncoder(in interface{}) []byte {
+ resp,_ := in.(protocal.AbstractBranchEndResponse)
+ data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ if resp.Xid != "" {
+ w.WriteInt16(int16(len(resp.Xid)))
+ w.WriteString(resp.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ w.WriteInt64(resp.BranchId)
+ w.WriteByte(byte(resp.BranchStatus))
+
+ result := append(data,b.Bytes()...)
+
+ return result
+}
+
+func AbstractGlobalEndRequestEncoder(in interface{}) []byte {
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.AbstractGlobalEndRequest)
+
+ if req.Xid != "" {
+ w.WriteInt16(int16(len(req.Xid)))
+ w.WriteString(req.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+ if req.ExtraData != nil {
+ w.WriteUint16(uint16(len(req.ExtraData)))
+ w.Write(req.ExtraData)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ return b.Bytes()
+}
+
+func AbstractGlobalEndResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.AbstractGlobalEndResponse)
+ data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+
+ result := append(data,byte(resp.GlobalStatus))
+
+ return result
+}
+
+func BranchCommitRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.BranchCommitRequest)
+ return AbstractBranchEndRequestEncoder(req.AbstractBranchEndRequest)
+}
+
+func BranchCommitResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.BranchCommitResponse)
+ return AbstractBranchEndResponseEncoder(resp.AbstractBranchEndResponse)
+}
+
+func BranchRegisterRequestEncoder(in interface{}) []byte {
+ var (
+ zero32 int32 = 0
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.BranchRegisterRequest)
+
+ if req.Xid != "" {
+ w.WriteInt16(int16(len(req.Xid)))
+ w.WriteString(req.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ w.WriteByte(byte(req.BranchType))
+
+ if req.ResourceId != "" {
+ w.WriteInt16(int16(len(req.ResourceId)))
+ w.WriteString(req.ResourceId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.LockKey != "" {
+ w.WriteInt32(int32(len(req.LockKey)))
+ w.WriteString(req.LockKey)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ if req.ApplicationData != nil {
+ w.WriteUint32(uint32(len(req.ApplicationData)))
+ w.Write(req.ApplicationData)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ return b.Bytes()
+}
+
+func BranchRegisterResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.BranchRegisterResponse)
+ data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+
+ c := uint64(resp.BranchId)
+ branchIdBytes := []byte{
+ byte(c >> 56),
+ byte(c >> 48),
+ byte(c >> 40),
+ byte(c >> 32),
+ byte(c >> 24),
+ byte(c >> 16),
+ byte(c >> 8),
+ byte(c),
+ }
+ result := append(data,branchIdBytes...)
+
+ return result
+}
+
+func BranchReportRequestEncoder(in interface{}) []byte {
+ var (
+ zero32 int32 = 0
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.BranchReportRequest)
+
+ if req.Xid != "" {
+ w.WriteInt16(int16(len(req.Xid)))
+ w.WriteString(req.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ w.WriteInt64(req.BranchId)
+ w.WriteByte(byte(req.Status))
+
+ if req.ResourceId != "" {
+ w.WriteInt16(int16(len(req.ResourceId)))
+ w.WriteString(req.ResourceId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if req.ApplicationData != nil {
+ w.WriteUint32(uint32(len(req.ApplicationData)))
+ w.Write(req.ApplicationData)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ w.WriteByte(byte(req.BranchType))
+
+ return b.Bytes()
+}
+
+func BranchReportResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.BranchReportResponse)
+ return AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+}
+
+func BranchRollbackRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.BranchRollbackRequest)
+ return AbstractBranchEndRequestEncoder(req.AbstractBranchEndRequest)
+}
+
+func BranchRollbackResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.BranchRollbackResponse)
+ return AbstractBranchEndResponseEncoder(resp.AbstractBranchEndResponse)
+}
+
+func GlobalBeginRequestEncoder(in interface{}) []byte {
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.GlobalBeginRequest)
+
+ w.WriteInt32(req.Timeout)
+ if req.TransactionName != "" {
+ w.WriteInt16(int16(len(req.TransactionName)))
+ w.WriteString(req.TransactionName)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ return b.Bytes()
+}
+
+func GlobalBeginResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.GlobalBeginResponse)
+ data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ if resp.Xid != "" {
+ w.WriteInt16(int16(len(resp.Xid)))
+ w.WriteString(resp.Xid)
+ } else {
+ w.WriteInt16(zero16)
+ }
+ if resp.ExtraData != nil {
+ w.WriteUint16(uint16(len(resp.ExtraData)))
+ w.Write(resp.ExtraData)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ result := append(data,b.Bytes()...)
+
+ return result
+}
+
+func GlobalCommitRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.GlobalCommitRequest)
+ return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest)
+}
+
+func GlobalCommitResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.GlobalCommitResponse)
+ return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse)
+}
+
+func GlobalLockQueryRequestEncoder(in interface{}) []byte {
+ return BranchRegisterRequestEncoder(in)
+}
+
+func GlobalLockQueryResponseEncoder(in interface{}) []byte {
+ resp,_ := in.(protocal.GlobalLockQueryResponse)
+ data := AbstractTransactionResponseEncoder(resp.AbstractTransactionResponse)
+
+ var result []byte
+ if resp.Lockable {
+ result = append(data,byte(0),byte(1))
+ } else {
+ result = append(data,byte(0),byte(0))
+ }
+
+ return result
+}
+
+func GlobalReportRequestEncoder(in interface{}) []byte {
+ req,_ := in.(protocal.GlobalReportRequest)
+ data := AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest)
+
+ result := append(data,byte(req.GlobalStatus))
+ return result
+}
+
+func GlobalReportResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.GlobalReportResponse)
+ return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse)
+}
+
+func GlobalRollbackRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.GlobalRollbackRequest)
+ return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest)
+}
+
+func GlobalRollbackResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.GlobalRollbackResponse)
+ return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse)
+}
+
+func GlobalStatusRequestEncoder(in interface{}) []byte {
+ req := in.(protocal.GlobalStatusRequest)
+ return AbstractGlobalEndRequestEncoder(req.AbstractGlobalEndRequest)
+}
+
+func GlobalStatusResponseEncoder(in interface{}) []byte {
+ resp := in.(protocal.GlobalStatusResponse)
+ return AbstractGlobalEndResponseEncoder(resp.AbstractGlobalEndResponse)
+}
+
+func UndoLogDeleteRequestEncoder(in interface{}) []byte {
+ var (
+ zero16 int16 = 0
+ b bytes.Buffer
+ )
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ req,_ := in.(protocal.UndoLogDeleteRequest)
+
+ w.WriteByte(byte(req.BranchType))
+ if req.ResourceId != "" {
+ w.WriteInt16(int16(len(req.ResourceId)))
+ w.WriteString(req.ResourceId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+ w.WriteInt16(req.SaveDays)
+
+ return b.Bytes()
+}
\ No newline at end of file
diff --git a/protocal/constant.go b/protocal/constant.go
new file mode 100644
index 0000000..c170cc3
--- /dev/null
+++ b/protocal/constant.go
@@ -0,0 +1,13 @@
+package protocal
+
+var MAGIC_CODE_BYTES = [2]byte{ 0xda, 0xda }
+const (
+ VERSION = 1
+ MAX_FRAME_LENGTH = 8 * 1024 * 1024
+ V1_HEAD_LENGTH = 16
+ MSGTYPE_RESQUEST = 0
+ MSGTYPE_RESPONSE = 1
+ MSGTYPE_RESQUEST_ONEWAY = 2
+ MSGTYPE_HEARTBEAT_REQUEST = 3
+ MSGTYPE_HEARTBEAT_RESPONSE = 4
+)
diff --git a/protocal/heart_beat_message.go b/protocal/heart_beat_message.go
new file mode 100644
index 0000000..a7acfc7
--- /dev/null
+++ b/protocal/heart_beat_message.go
@@ -0,0 +1,16 @@
+package protocal
+
+type HeartBeatMessage struct {
+ Ping bool
+}
+
+var HeartBeatMessagePing = HeartBeatMessage{true}
+var HeartBeatMessagePong = HeartBeatMessage{false}
+
+func (msg HeartBeatMessage) ToString () string {
+ if msg.Ping {
+ return "services ping"
+ } else {
+ return "services pong"
+ }
+}
\ No newline at end of file
diff --git a/protocal/identify.go b/protocal/identify.go
new file mode 100644
index 0000000..3c05aa8
--- /dev/null
+++ b/protocal/identify.go
@@ -0,0 +1,26 @@
+package protocal
+
+type AbstractResultMessage struct {
+ ResultCode ResultCode
+ Msg string
+}
+
+type AbstractIdentifyRequest struct {
+ Version string
+
+ ApplicationId string
+
+ TransactionServiceGroup string
+
+ ExtraData []byte
+}
+
+type AbstractIdentifyResponse struct {
+ AbstractResultMessage
+
+ Version string
+
+ ExtraData []byte
+
+ Identified bool
+}
\ No newline at end of file
diff --git a/protocal/merged_message.go b/protocal/merged_message.go
new file mode 100644
index 0000000..ec16a92
--- /dev/null
+++ b/protocal/merged_message.go
@@ -0,0 +1,17 @@
+package protocal
+
+type MergedWarpMessage struct {
+ Msgs []MessageTypeAware
+ MsgIds []int32
+}
+
+func (req MergedWarpMessage) GetTypeCode() int16 {
+ return TypeSeataMerge
+}
+type MergeResultMessage struct {
+ Msgs []MessageTypeAware
+}
+
+func (resp MergeResultMessage) GetTypeCode() int16 {
+ return TypeSeataMergeResult
+}
\ No newline at end of file
diff --git a/protocal/message_type.go b/protocal/message_type.go
new file mode 100644
index 0000000..733d2d0
--- /dev/null
+++ b/protocal/message_type.go
@@ -0,0 +1,117 @@
+package protocal
+
+type MessageType int16
+
+const (
+ /**
+ * The constant TYPE_GLOBAL_BEGIN.
+ */
+ TypeGlobalBegin = 1
+ /**
+ * The constant TYPE_GLOBAL_BEGIN_RESULT.
+ */
+ TypeGlobalBeginResult = 2
+ /**
+ * The constant TYPE_GLOBAL_COMMIT.
+ */
+ TypeGlobalCommit = 7
+ /**
+ * The constant TYPE_GLOBAL_COMMIT_RESULT.
+ */
+ TypeGlobalCommitResult = 8
+ /**
+ * The constant TYPE_GLOBAL_ROLLBACK.
+ */
+ TypeGlobalRollback = 9
+ /**
+ * The constant TYPE_GLOBAL_ROLLBACK_RESULT.
+ */
+ TypeGlobalRollbackResult = 10
+ /**
+ * The constant TYPE_GLOBAL_STATUS.
+ */
+ TypeGlobalStatus = 15
+ /**
+ * The constant TYPE_GLOBAL_STATUS_RESULT.
+ */
+ TypeGlobalStatusResult = 16
+ /**
+ * The constant TYPE_GLOBAL_REPORT.
+ */
+ TypeGlobalReport = 17
+ /**
+ * The constant TYPE_GLOBAL_REPORT_RESULT.
+ */
+ TypeGlobalReportResult = 18
+ /**
+ * The constant TYPE_GLOBAL_LOCK_QUERY.
+ */
+ TypeGlobalLockQuery = 21
+ /**
+ * The constant TYPE_GLOBAL_LOCK_QUERY_RESULT.
+ */
+ TypeGlobalLockQueryResult = 22
+
+ /**
+ * The constant TYPE_BRANCH_COMMIT.
+ */
+ TypeBranchCommit = 3
+ /**
+ * The constant TYPE_BRANCH_COMMIT_RESULT.
+ */
+ TypeBranchCommitResult = 4
+ /**
+ * The constant TYPE_BRANCH_ROLLBACK.
+ */
+ TypeBranchRollback = 5
+ /**
+ * The constant TYPE_BRANCH_ROLLBACK_RESULT.
+ */
+ TypeBranchRollbackResult = 6
+ /**
+ * The constant TYPE_BRANCH_REGISTER.
+ */
+ TypeBranchRegister = 11
+ /**
+ * The constant TYPE_BRANCH_REGISTER_RESULT.
+ */
+ TypeBranchRegisterResult = 12
+ /**
+ * The constant TYPE_BRANCH_STATUS_REPORT.
+ */
+ TypeBranchStatusReport = 13
+ /**
+ * The constant TYPE_BRANCH_STATUS_REPORT_RESULT.
+ */
+ TypeBranchStatusReportResult = 14
+
+ /**
+ * The constant TYPE_SEATA_MERGE.
+ */
+ TypeSeataMerge = 59
+ /**
+ * The constant TYPE_SEATA_MERGE_RESULT.
+ */
+ TypeSeataMergeResult = 60
+
+ /**
+ * The constant TYPE_REG_CLT.
+ */
+ TypeRegClt = 101
+ /**
+ * The constant TYPE_REG_CLT_RESULT.
+ */
+ TypeRegCltResult = 102
+ /**
+ * The constant TYPE_REG_RM.
+ */
+ TypeRegRm = 103
+ /**
+ * The constant TYPE_REG_RM_RESULT.
+ */
+ TypeRegRmResult = 104
+ /**
+ * The constant TYPE_RM_DELETE_UNDOLOG.
+ */
+ TypeRmDeleteUndolog = 111
+)
diff --git a/protocal/message_type_aware.go b/protocal/message_type_aware.go
new file mode 100644
index 0000000..8bdcd7f
--- /dev/null
+++ b/protocal/message_type_aware.go
@@ -0,0 +1,5 @@
+package protocal
+
+type MessageTypeAware interface {
+ GetTypeCode() int16
+}
diff --git a/protocal/result_code.go b/protocal/result_code.go
new file mode 100644
index 0000000..bc84081
--- /dev/null
+++ b/protocal/result_code.go
@@ -0,0 +1,18 @@
+package protocal
+
+type ResultCode byte
+
+const (
+
+ /**
+ * ResultCodeFailed result code.
+ */
+ // ResultCodeFailed
+ ResultCodeFailed ResultCode = iota
+
+ /**
+ * Success result code.
+ */
+ // Success
+ ResultCodeSuccess
+)
diff --git a/protocal/rm.go b/protocal/rm.go
new file mode 100644
index 0000000..62c3e2c
--- /dev/null
+++ b/protocal/rm.go
@@ -0,0 +1,18 @@
+package protocal
+
+type RegisterRMRequest struct {
+ AbstractIdentifyRequest
+ ResourceIds string
+}
+
+func (req RegisterRMRequest) GetTypeCode() int16 {
+ return TypeRegRm
+}
+
+type RegisterRMResponse struct {
+ AbstractIdentifyResponse
+}
+
+func (resp RegisterRMResponse) GetTypeCode() int16 {
+ return TypeRegRmResult
+}
\ No newline at end of file
diff --git a/protocal/rpc_message.go b/protocal/rpc_message.go
new file mode 100644
index 0000000..eb70b86
--- /dev/null
+++ b/protocal/rpc_message.go
@@ -0,0 +1,10 @@
+package protocal
+
+type RpcMessage struct {
+ Id int32
+ MessageType byte
+ Codec byte
+ Compressor byte
+ HeadMap map[string]string
+ Body interface{}
+}
diff --git a/protocal/tm.go b/protocal/tm.go
new file mode 100644
index 0000000..f962128
--- /dev/null
+++ b/protocal/tm.go
@@ -0,0 +1,17 @@
+package protocal
+
+type RegisterTMRequest struct {
+ AbstractIdentifyRequest
+}
+
+func (req RegisterTMRequest) GetTypeCode() int16 {
+ return TypeRegClt
+}
+
+type RegisterTMResponse struct {
+ AbstractIdentifyResponse
+}
+
+func (resp RegisterTMResponse) GetTypeCode() int16 {
+ return TypeRegCltResult
+}
\ No newline at end of file
diff --git a/protocal/transaction.go b/protocal/transaction.go
new file mode 100644
index 0000000..1624b5a
--- /dev/null
+++ b/protocal/transaction.go
@@ -0,0 +1,227 @@
+package protocal
+
+import (
+ "github.com/dk-lockdown/seata-golang/meta"
+)
+
+type AbstractTransactionResponse struct {
+ AbstractResultMessage
+ TransactionExceptionCode meta.TransactionExceptionCode
+}
+
+type AbstractBranchEndRequest struct {
+ Xid string
+ BranchId int64
+ BranchType meta.BranchType
+ ResourceId string
+ ApplicationData []byte
+}
+
+type AbstractBranchEndResponse struct {
+ AbstractTransactionResponse
+
+ Xid string
+ BranchId int64
+ BranchStatus meta.BranchStatus
+}
+
+type AbstractGlobalEndRequest struct {
+ Xid string
+ ExtraData []byte
+}
+
+
+type AbstractGlobalEndResponse struct {
+ AbstractTransactionResponse
+
+ GlobalStatus meta.GlobalStatus
+}
+
+type BranchRegisterRequest struct {
+ Xid string
+ BranchType meta.BranchType
+ ResourceId string
+ LockKey string
+ ApplicationData []byte
+}
+
+func (req BranchRegisterRequest) GetTypeCode() int16 {
+ return TypeBranchRegister
+}
+
+type BranchRegisterResponse struct {
+ AbstractTransactionResponse
+
+ BranchId int64
+}
+
+func (resp BranchRegisterResponse) GetTypeCode() int16 {
+ return TypeBranchRegisterResult
+}
+
+type BranchReportRequest struct {
+ Xid string
+ BranchId int64
+ ResourceId string
+ Status meta.BranchStatus
+ ApplicationData []byte
+ BranchType meta.BranchType
+}
+
+func (req BranchReportRequest) GetTypeCode() int16 {
+ return TypeBranchStatusReport
+}
+
+type BranchReportResponse struct {
+ AbstractTransactionResponse
+}
+
+func (resp BranchReportResponse) GetTypeCode() int16 {
+ return TypeBranchStatusReportResult
+}
+
+type BranchCommitRequest struct {
+ AbstractBranchEndRequest
+}
+
+func (req BranchCommitRequest) GetTypeCode() int16 {
+ return TypeBranchCommit
+}
+
+type BranchCommitResponse struct {
+ AbstractBranchEndResponse
+}
+
+func (resp BranchCommitResponse) GetTypeCode() int16 {
+ return TypeBranchCommitResult
+}
+
+type BranchRollbackRequest struct {
+ AbstractBranchEndRequest
+}
+
+func (req BranchRollbackRequest) GetTypeCode() int16 {
+ return TypeBranchRollback
+}
+
+type BranchRollbackResponse struct {
+ AbstractBranchEndResponse
+}
+
+func (resp BranchRollbackResponse) GetTypeCode() int16 {
+ return TypeGlobalRollbackResult
+}
+
+type GlobalBeginRequest struct {
+ Timeout int32
+ TransactionName string
+}
+
+func (req GlobalBeginRequest) GetTypeCode() int16 {
+ return TypeGlobalBegin
+}
+
+type GlobalBeginResponse struct {
+ AbstractTransactionResponse
+
+ Xid string
+ ExtraData []byte
+}
+
+func (resp GlobalBeginResponse) GetTypeCode() int16 {
+ return TypeGlobalBeginResult
+}
+
+type GlobalStatusRequest struct {
+ AbstractGlobalEndRequest
+}
+
+func (req GlobalStatusRequest) GetTypeCode() int16 {
+ return TypeGlobalStatus
+}
+
+type GlobalStatusResponse struct {
+ AbstractGlobalEndResponse
+}
+
+func (resp GlobalStatusResponse) GetTypeCode() int16 {
+ return TypeGlobalStatusResult
+}
+
+type GlobalLockQueryRequest struct {
+ BranchRegisterRequest
+}
+
+func (req GlobalLockQueryRequest) GetTypeCode() int16 {
+ return TypeGlobalLockQuery
+}
+
+type GlobalLockQueryResponse struct {
+ AbstractTransactionResponse
+
+ Lockable bool
+}
+
+func (resp GlobalLockQueryResponse) GetTypeCode() int16 {
+ return TypeGlobalLockQueryResult
+}
+
+type GlobalReportRequest struct {
+ AbstractGlobalEndRequest
+
+ GlobalStatus meta.GlobalStatus
+}
+
+func (req GlobalReportRequest) GetTypeCode() int16 {
+ return TypeGlobalStatus
+}
+
+type GlobalReportResponse struct {
+ AbstractGlobalEndResponse
+}
+
+func (resp GlobalReportResponse) GetTypeCode() int16 {
+ return TypeGlobalStatusResult
+}
+
+type GlobalCommitRequest struct {
+ AbstractGlobalEndRequest
+}
+
+func (req GlobalCommitRequest) GetTypeCode() int16 {
+ return TypeGlobalCommit
+}
+
+type GlobalCommitResponse struct {
+ AbstractGlobalEndResponse
+}
+
+func (resp GlobalCommitResponse) GetTypeCode() int16 {
+ return TypeGlobalCommitResult
+}
+
+type GlobalRollbackRequest struct {
+ AbstractGlobalEndRequest
+}
+
+func (req GlobalRollbackRequest) GetTypeCode() int16 {
+ return TypeGlobalRollback
+}
+
+type GlobalRollbackResponse struct {
+ AbstractGlobalEndResponse
+}
+
+func (resp GlobalRollbackResponse) GetTypeCode() int16 {
+ return TypeGlobalRollbackResult
+}
+
+type UndoLogDeleteRequest struct {
+ ResourceId string
+ SaveDays int16
+ BranchType meta.BranchType
+}
+
+func (req UndoLogDeleteRequest) GetTypeCode() int16 {
+ return TypeRmDeleteUndolog
+}
\ No newline at end of file
diff --git a/rm/resource_manager.go b/rm/resource_manager.go
new file mode 100644
index 0000000..bcf3493
--- /dev/null
+++ b/rm/resource_manager.go
@@ -0,0 +1,109 @@
+package rm
+
+import (
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/model"
+)
+
+type IResourceManagerInbound interface {
+ /**
+ * Commit a branch transaction.
+ *
+ * @param branchType the branch type
+ * @param xid Transaction id.
+ * @param branchId Branch id.
+ * @param resourceId Resource id.
+ * @param applicationData Application data bind with this branch.
+ * @return Status of the branch after committing.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ BranchCommit(branchType meta.BranchType, xid string, branchId int64, resourceId string, applicationData []byte) (meta.BranchStatus, error)
+
+ /**
+ * Rollback a branch transaction.
+ *
+ * @param branchType the branch type
+ * @param xid Transaction id.
+ * @param branchId Branch id.
+ * @param resourceId Resource id.
+ * @param applicationData Application data bind with this branch.
+ * @return Status of the branch after rollbacking.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ BranchRollback(branchType meta.BranchType, xid string, branchId int64, resourceId string, applicationData []byte) (meta.BranchStatus, error)
+}
+
+type IResourceManagerOutbound interface {
+ /**
+ * Branch register long.
+ *
+ * @param branchType the branch type
+ * @param resourceId the resource id
+ * @param clientId the client id
+ * @param xid the xid
+ * @param applicationData the context
+ * @param lockKeys the lock keys
+ * @return the long
+ * @throws TransactionException the transaction exception
+ */
+ BranchRegister(branchType meta.BranchType, resourceId string, clientId string, xid string, applicationData []byte, lockKeys string) (int64, error)
+
+ /**
+ * Branch report.
+ *
+ * @param branchType the branch type
+ * @param xid the xid
+ * @param branchId the branch id
+ * @param status the status
+ * @param applicationData the application data
+ * @throws TransactionException the transaction exception
+ */
+ BranchReport(branchType meta.BranchType, xid string, branchId int64, status meta.BranchStatus, applicationData []byte) error
+
+ /**
+ * Lock query boolean.
+ *
+ * @param branchType the branch type
+ * @param resourceId the resource id
+ * @param xid the xid
+ * @param lockKeys the lock keys
+ * @return the boolean
+ * @throws TransactionException the transaction exception
+ */
+ LockQuery(branchType meta.BranchType, resourceId string, xid string, lockKeys string) (bool, error)
+}
+
+type IResourceManager interface {
+ IResourceManagerInbound
+ IResourceManagerOutbound
+
+ /**
+ * Register a Resource to be managed by Resource Manager.
+ *
+ * @param resource The resource to be managed.
+ */
+ registerResource(resource model.IResource)
+
+ /**
+ * Unregister a Resource from the Resource Manager.
+ *
+ * @param resource The resource to be removed.
+ */
+ unregisterResource(resource model.IResource)
+
+ /**
+ * Get all resources managed by this manager.
+ *
+ * @return resourceId -> Resource Map
+ */
+ getManagedResources() map[string]model.IResource
+
+ /**
+ * Get the BranchType.
+ *
+ * @return The BranchType of ResourceManager.
+ */
+ getBranchType() meta.BranchType
+}
\ No newline at end of file
diff --git a/tc/app/cmd/main.go b/tc/app/cmd/main.go
new file mode 100644
index 0000000..df813cc
--- /dev/null
+++ b/tc/app/cmd/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+ "os"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/tc/server"
+)
+
+const (
+ APP_CONF_FILE = "APP_CONF_FILE"
+)
+
+func main() {
+ confFile := os.Getenv(APP_CONF_FILE)
+ config.InitConf(confFile)
+ server.SetServerGrpool()
+ srv := server.NewServer()
+ conf := config.GetServerConfig()
+ srv.Start(conf.Host+":"+conf.Port)
+}
diff --git a/tc/app/profiles/dev/config.yml b/tc/app/profiles/dev/config.yml
new file mode 100644
index 0000000..f449844
--- /dev/null
+++ b/tc/app/profiles/dev/config.yml
@@ -0,0 +1,28 @@
+host: "127.0.0.1"
+port: "8091"
+timeout_retry_period: "1s"
+rollbacking_retry_period: "1s"
+committing_retry_period: "1s"
+aync_committing_retry_period: "1s"
+log_delete_period: "24h"
+getty_config:
+ session_timeout : "20s"
+ session_number : 700
+ getty_session_param:
+ compress_encoding : false
+ tcp_no_delay : true
+ tcp_keep_alive : true
+ keep_alive_period : "120s"
+ tcp_r_buf_size : 262144
+ tcp_w_buf_size : 524288
+ pkg_rq_size : 1024
+ pkg_wq_size : 512
+ tcp_read_timeout : "1s"
+ tcp_write_timeout : "5s"
+ wait_timeout : "1s"
+ max_msg_len : 4096
+ session_name : "seata-server"
+
+store_config:
+ max_global_session_size: 512
+ max_branch_session_size: 16384
\ No newline at end of file
diff --git a/tc/config/getty_config.go b/tc/config/getty_config.go
new file mode 100644
index 0000000..015c026
--- /dev/null
+++ b/tc/config/getty_config.go
@@ -0,0 +1,38 @@
+package config
+
+import "time"
+
+type GettySessionParam struct {
+ CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"`
+ TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"`
+ TcpKeepAlive bool `default:"true" yaml:"tcp_keep_alive" json:"tcp_keep_alive,omitempty"`
+ KeepAlivePrd string `default:"180s" yaml:"keep_alive_period" json:"keep_alive_period,omitempty"`
+ KeepAlivePeriod time.Duration
+ TcpRBufSize int `default:"262144" yaml:"tcp_r_buf_size" json:"tcp_r_buf_size,omitempty"`
+ TcpWBufSize int `default:"65536" yaml:"tcp_w_buf_size" json:"tcp_w_buf_size,omitempty"`
+ PkgWQSize int `default:"1024" yaml:"pkg_wq_size" json:"pkg_wq_size,omitempty"`
+ TcpReadTmt string `default:"1s" yaml:"tcp_read_timeout" json:"tcp_read_timeout,omitempty"`
+ TcpReadTimeout time.Duration
+ TcpWriteTmt string `default:"5s" yaml:"tcp_write_timeout" json:"tcp_write_timeout,omitempty"`
+ TcpWriteTimeout time.Duration
+ WaitTmt string `default:"7s" yaml:"wait_timeout" json:"wait_timeout,omitempty"`
+ WaitTimeout time.Duration
+ MaxMsgLen int `default:"1024" yaml:"max_msg_len" json:"max_msg_len,omitempty"`
+ SessionName string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"`
+}
+
+// Config holds supported types by the multiconfig package
+type GettyConfig struct {
+ // session
+ SessionTmt string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"`
+ SessionTimeout time.Duration
+ SessionNumber int `default:"1000" yaml:"session_number" json:"session_number,omitempty"`
+
+ // grpool
+ GrPoolSize int `default:"0" yaml:"gr_pool_size" json:"gr_pool_size,omitempty"`
+ QueueLen int `default:"0" yaml:"queue_len" json:"queue_len,omitempty"`
+ QueueNumber int `default:"0" yaml:"queue_number" json:"queue_number,omitempty"`
+
+ // session tcp parameters
+ GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"`
+}
\ No newline at end of file
diff --git a/tc/config/server_config.go b/tc/config/server_config.go
new file mode 100644
index 0000000..a637a69
--- /dev/null
+++ b/tc/config/server_config.go
@@ -0,0 +1,104 @@
+package config
+
+import (
+ "fmt"
+ "github.com/pkg/errors"
+ "gopkg.in/yaml.v2"
+ "io/ioutil"
+ "path"
+ "time"
+)
+
+var (
+ conf ServerConfig
+)
+
+func GetServerConfig() ServerConfig {
+ return conf
+}
+
+type ServerConfig struct {
+ Host string `default:"127.0.0.1" yaml:"host" json:"host,omitempty"`
+ Port string `default:"8091" yaml:"port" json:"port,omitempty"`
+ MaxRollbackRetryTimeout int64 `default:"-1" yaml:"max_rollback_retry_timeout" json:"max_rollback_retry_timeout,omitempty"`
+ RollbackRetryTimeoutUnlockEnable bool `default:"false" yaml:"rollback_retry_timeout_unlock_enable" json:"rollback_retry_timeout_unlock_enable,omitempty"`
+ MaxCommitRetryTimeout int64 `default:"-1" yaml:"max_commit_retry_timeout" json:"max_commit_retry_timeout,omitempty"`
+ TimeoutRetryPrd string `default:"1s" yaml:"timeout_retry_period" json:"timeout_retry_period,omitempty"`
+ TimeoutRetryPeriod time.Duration
+ RollbackingRetryPrd string `default:"1s" yaml:"rollbacking_retry_period" json:"rollbacking_retry_period,omitempty"`
+ RollbackingRetryPeriod time.Duration
+ CommittingRetryPrd string `default:"1s" yaml:"committing_retry_period" json:"committing_retry_period,omitempty"`
+ CommittingRetryPeriod time.Duration
+ AsynCommittingRetryPrd string `default:"1s" yaml:"aync_committing_retry_period" json:"aync_committing_retry_period,omitempty"`
+ AsynCommittingRetryPeriod time.Duration
+ LogDeletePrd string `default:"24h" yaml:"log_delete_period" json:"log_delete_period,omitempty"`
+ LogDeletePeriod time.Duration
+ GettyConfig GettyConfig `required:"true" yaml:"getty_config" json:"getty_config,omitempty"`
+ UndoConfig UndoConfig `required:"true" yaml:"undo_config" json:"undo_config,omitempty"`
+ StoreConfig StoreConfig `required:"true" yaml:"store_config" json:"store_config,omitempty"`
+}
+
+
+func InitConf(confFile string) error {
+ var err error
+
+ if confFile == "" {
+ return errors.WithMessagef(err,fmt.Sprintf("application configure file name is nil"))
+ }
+ if path.Ext(confFile) != ".yml" {
+ return errors.WithMessagef(err,fmt.Sprintf("application configure file name{%v} suffix must be .yml", confFile))
+ }
+
+ conf = ServerConfig{}
+ confFileStream, err := ioutil.ReadFile(confFile)
+ if err != nil {
+ return errors.WithMessagef(err,fmt.Sprintf("ioutil.ReadFile(file:%s) = error:%s", confFile, err))
+ }
+ err = yaml.Unmarshal(confFileStream, &conf)
+ if err != nil {
+ return errors.WithMessagef(err,fmt.Sprintf("yaml.Unmarshal() = error:%s", err))
+ }
+
+ if conf.TimeoutRetryPeriod, err = time.ParseDuration(conf.TimeoutRetryPrd); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(TimeoutRetryPrd{%#v})", conf.TimeoutRetryPrd)
+ }
+
+ conf.RollbackingRetryPeriod, err = time.ParseDuration(conf.RollbackingRetryPrd)
+ if err != nil {
+ return errors.WithMessagef(err,"time.ParseDuration(RollbackingRetryPrd{%#v})", conf.RollbackingRetryPrd)
+ }
+ if conf.CommittingRetryPeriod, err = time.ParseDuration(conf.CommittingRetryPrd); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(CommittingRetryPrd{%#v})", conf.CommittingRetryPrd)
+ }
+
+ if conf.AsynCommittingRetryPeriod, err = time.ParseDuration(conf.AsynCommittingRetryPrd); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(AsynCommittingRetryPrd{%#v})", conf.AsynCommittingRetryPrd)
+ }
+
+ if conf.LogDeletePeriod, err = time.ParseDuration(conf.LogDeletePrd); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(LogDeletePrd{%#v})", conf.LogDeletePrd)
+ }
+
+ conf.GettyConfig.SessionTimeout, err = time.ParseDuration(conf.GettyConfig.SessionTmt)
+ if err != nil {
+ return errors.WithMessagef(err,fmt.Sprintf("time.ParseDuration(SessionTimeout{%#v}) = error{%v}", conf.GettyConfig.SessionTmt, err))
+ }
+ if conf.GettyConfig.GettySessionParam.KeepAlivePeriod, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.KeepAlivePrd); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(KeepAlivePeriod{%#v})", conf.GettyConfig.GettySessionParam.KeepAlivePrd)
+ }
+
+ if conf.GettyConfig.GettySessionParam.TcpReadTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.TcpReadTmt); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(TcpReadTimeout{%#v})", conf.GettyConfig.GettySessionParam.TcpReadTmt)
+ }
+
+ if conf.GettyConfig.GettySessionParam.TcpWriteTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.TcpWriteTmt); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(TcpWriteTimeout{%#v})", conf.GettyConfig.GettySessionParam.TcpWriteTmt)
+ }
+
+ if conf.GettyConfig.GettySessionParam.WaitTimeout, err = time.ParseDuration(conf.GettyConfig.GettySessionParam.WaitTmt); err != nil {
+ return errors.WithMessagef(err, "time.ParseDuration(WaitTimeout{%#v})", conf.GettyConfig.GettySessionParam.WaitTmt)
+ }
+
+ storeConfig = conf.StoreConfig
+ return nil
+}
diff --git a/tc/config/store_config.go b/tc/config/store_config.go
new file mode 100644
index 0000000..bf08a61
--- /dev/null
+++ b/tc/config/store_config.go
@@ -0,0 +1,58 @@
+package config
+
+const (
+ DefaultFileDir = "root.data"
+ DefaultMaxBranchSessionSize = 1024 * 16
+ DefaultMaxGlobalSessionSize = 512
+ DefaultWriteBufferSize = 1024 * 16
+ DefualtServiceSessionReloadReadSize = 100
+)
+
+type FlushDiskMode int
+
+const (
+ /**
+ * sync flush disk
+ */
+ FlushdiskModeSyncModel FlushDiskMode = iota
+
+ /**
+ * async flush disk
+ */
+ FlushdiskModeAsyncModel
+)
+
+type StoreConfig struct {
+ MaxBranchSessionSize int `default:"16384" yaml:"max_branch_session_size" json:"max_branch_session_size,omitempty"`
+ MaxGlobalSessionSize int `default:"512" yaml:"max_global_session_size" json:"max_global_session_size,omitempty"`
+ StoreMode string `default:"file" yaml:"mode" json:"mode,omitempty"`
+ FileStoreConfig FileStoreConfig `yaml:"file" json:"file,omitempty"`
+ DBStoreConfig DBStoreConfig `yaml:"db" json:"db,omitempty"`
+}
+
+type FileStoreConfig struct {
+ FileDir string `default:"root.data" yaml:"file_dir" json:"file_dir,omitempty"`
+ FileWriteBufferCacheSize int `default:"16384" yaml:"file_write_buffer_cache_size" json:"file_write_buffer_cache_size,omitempty"`
+ FlushDiskMode FlushDiskMode `default:"1" yaml:"flush_disk_mode" json:"flush_disk_mode,omitempty"`
+ SessionReloadReadSize int `default:"100" yaml:"session_reload_read_size" json:"session_reload_read_size,omitempty"`
+}
+
+type DBStoreConfig struct {
+
+}
+
+var storeConfig StoreConfig
+
+func GetStoreConfig() StoreConfig {
+ return storeConfig
+}
+
+func GetDefaultFileStoreConfig() FileStoreConfig{
+ return FileStoreConfig{
+ FileDir: DefaultFileDir,
+ FileWriteBufferCacheSize: DefaultWriteBufferSize,
+ FlushDiskMode: 0,
+ SessionReloadReadSize: DefualtServiceSessionReloadReadSize,
+ }
+}
+
diff --git a/tc/config/undo_config.go b/tc/config/undo_config.go
new file mode 100644
index 0000000..ca757d6
--- /dev/null
+++ b/tc/config/undo_config.go
@@ -0,0 +1,5 @@
+package config
+
+type UndoConfig struct {
+ LogSaveDays int16 `default:"7" yaml:"log_save_days" json:"log_save_days,omitempty"`
+}
\ No newline at end of file
diff --git a/tc/event/event_manager.go b/tc/event/event_manager.go
new file mode 100644
index 0000000..8f0c3f4
--- /dev/null
+++ b/tc/event/event_manager.go
@@ -0,0 +1,11 @@
+package event
+
+type EventManager struct {
+ GlobalTransactionEventChannel chan GlobalTransactionEvent
+}
+
+var EventBus EventManager
+
+func init() {
+ EventBus = EventManager{GlobalTransactionEventChannel:make(chan GlobalTransactionEvent,1000)}
+}
\ No newline at end of file
diff --git a/tc/event/global_transaction_event.go b/tc/event/global_transaction_event.go
new file mode 100644
index 0000000..a369872
--- /dev/null
+++ b/tc/event/global_transaction_event.go
@@ -0,0 +1,41 @@
+package event
+
+import "github.com/dk-lockdown/seata-golang/meta"
+
+const (
+ RoleTC = "tc"
+ RoleTM = "tm"
+ RoleRM = "rm"
+)
+
+type GlobalTransactionEvent struct {
+ id int64
+ role string
+ name string
+ beginTime int64
+ endTime int64
+ status meta.GlobalStatus
+}
+
+func NewGlobalTransactionEvent(id int64,role string,name string,beginTime int64,endTime int64,status meta.GlobalStatus) GlobalTransactionEvent {
+ return GlobalTransactionEvent{
+ id,
+ role,
+ name,
+ beginTime,
+ endTime,
+ status,
+ }
+}
+
+func (event GlobalTransactionEvent) GetId() int64 { return event.id }
+
+func (event GlobalTransactionEvent) GetRole() string { return event.role }
+
+func (event GlobalTransactionEvent) GetName() string { return event.name }
+
+func (event GlobalTransactionEvent) GetBeginTime() int64 { return event.beginTime }
+
+func (event GlobalTransactionEvent) GetEndTime() int64 { return event.endTime }
+
+func (event GlobalTransactionEvent) GetStatus() meta.GlobalStatus { return event.status }
\ No newline at end of file
diff --git a/tc/holder/default_session_manager.go b/tc/holder/default_session_manager.go
new file mode 100644
index 0000000..f766dda
--- /dev/null
+++ b/tc/holder/default_session_manager.go
@@ -0,0 +1,66 @@
+package holder
+
+import (
+ "github.com/dk-lockdown/seata-golang/tc/model"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+)
+
+type DefaultSessionManager struct {
+ AbstractSessionManager
+ SessionMap map[string]*session.GlobalSession
+}
+
+func NewDefaultSessionManager(name string) ISessionManager {
+ return &DefaultSessionManager{
+ AbstractSessionManager: AbstractSessionManager {
+ TransactionStoreManager: &AbstractTransactionStoreManager{},
+ Name: name,
+ },
+ SessionMap: make(map[string]*session.GlobalSession),
+ }
+}
+
+func (sessionManager *DefaultSessionManager) AddGlobalSession(session *session.GlobalSession) error {
+ sessionManager.AbstractSessionManager.AddGlobalSession(session)
+ sessionManager.SessionMap[session.Xid] = session
+ return nil
+}
+
+
+func (sessionManager *DefaultSessionManager) FindGlobalSession(xid string) *session.GlobalSession {
+ return sessionManager.SessionMap[xid]
+}
+
+func (sessionManager *DefaultSessionManager) FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession {
+ return sessionManager.SessionMap[xid]
+}
+
+func (sessionManager *DefaultSessionManager) RemoveGlobalSession(session *session.GlobalSession) error{
+ sessionManager.AbstractSessionManager.RemoveGlobalSession(session)
+ delete(sessionManager.SessionMap,session.Xid)
+ return nil
+}
+
+func (sessionManager *DefaultSessionManager) AllSessions() []*session.GlobalSession {
+ var sessions = make([]*session.GlobalSession,0)
+ for _,session := range sessionManager.SessionMap {
+ sessions = append(sessions,session)
+ }
+ return sessions
+}
+
+
+func (sessionManager *DefaultSessionManager) FindGlobalSessions(condition model.SessionCondition) []*session.GlobalSession {
+ var sessions = make([]*session.GlobalSession,0)
+ for _,session := range sessionManager.SessionMap {
+ if int64(util.CurrentTimeMillis()) - session.BeginTime > condition.OverTimeAliveMills {
+ sessions = append(sessions, session)
+ }
+ }
+ return sessions
+}
+
+func (sessionManager *DefaultSessionManager) SetTransactionStoreManager(transactionStoreManager ITransactionStoreManager) {
+ sessionManager.TransactionStoreManager = transactionStoreManager
+}
\ No newline at end of file
diff --git a/tc/holder/default_session_manager_test.go b/tc/holder/default_session_manager_test.go
new file mode 100644
index 0000000..b4a1262
--- /dev/null
+++ b/tc/holder/default_session_manager_test.go
@@ -0,0 +1,87 @@
+package holder
+
+import (
+ "github.com/stretchr/testify/assert"
+ "github.com/dk-lockdown/seata-golang/common"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+ "testing"
+)
+
+func TestDefaultSessionManager_AddGlobalSession_RemoveGlobalSession(t *testing.T) {
+ gs := globalSessionProvider()
+
+ sessionManager := NewDefaultSessionManager("default")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestDefaultSessionManager_FindGlobalSession(t *testing.T) {
+ gs := globalSessionProvider()
+ sessionManager := NewDefaultSessionManager("default")
+ sessionManager.AddGlobalSession(gs)
+ expected := sessionManager.FindGlobalSession(gs.Xid)
+
+ assert.NotNil(t,expected)
+ assert.Equal(t,gs.TransactionId,expected.TransactionId)
+ assert.Equal(t,gs.ApplicationId,expected.ApplicationId)
+ assert.Equal(t,gs.TransactionServiceGroup,expected.TransactionServiceGroup)
+ assert.Equal(t,gs.TransactionName,expected.TransactionName)
+ assert.Equal(t,gs.Status,expected.Status)
+
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func globalSessionsProvider() []*session.GlobalSession {
+ common.XID.IpAddress="127.0.0.1"
+ common.XID.Port=9876
+
+ result := make([]*session.GlobalSession,0)
+ gs1 := session.NewGlobalSession().
+ SetApplicationId("demo-app").
+ SetTransactionId(util.GeneratorUUID()).
+ SetTransactionServiceGroup("my_test_tx_group").
+ SetTransactionName("test").
+ SetTimeout(6000)
+ gs1.SetXid(common.XID.GenerateXID(gs1.TransactionId))
+
+ gs2 := session.NewGlobalSession().
+ SetApplicationId("demo-app").
+ SetTransactionId(util.GeneratorUUID()).
+ SetTransactionServiceGroup("my_test_tx_group").
+ SetTransactionName("test").
+ SetTimeout(6000)
+ gs2.SetXid(common.XID.GenerateXID(gs2.TransactionId))
+
+ result = append(result,gs1)
+ result = append(result,gs2)
+ return result
+}
+
+func globalSessionProvider() *session.GlobalSession {
+ common.XID.IpAddress="127.0.0.1"
+ common.XID.Port=9876
+
+ gs := session.NewGlobalSession().
+ SetApplicationId("demo-app").
+ SetTransactionId(util.GeneratorUUID()).
+ SetTransactionServiceGroup("my_test_tx_group").
+ SetTransactionName("test").
+ SetTimeout(6000)
+ gs.SetXid(common.XID.GenerateXID(gs.TransactionId))
+ return gs
+}
+
+func branchSessionProvider(globalSession *session.GlobalSession) *session.BranchSession {
+ bs := session.NewBranchSession().
+ SetTransactionId(globalSession.TransactionId).
+ SetBranchId(1).
+ SetResourceGroupId("my_test_tx_group").
+ SetResourceId("tb_1").
+ SetLockKey("t_1").
+ SetBranchType(meta.BranchTypeAT).
+ SetApplicationData([]byte("{\"data\":\"test\"}"))
+
+ return bs
+}
\ No newline at end of file
diff --git a/tc/holder/file_based_session_manager.go b/tc/holder/file_based_session_manager.go
new file mode 100644
index 0000000..bc38e3a
--- /dev/null
+++ b/tc/holder/file_based_session_manager.go
@@ -0,0 +1,215 @@
+package holder
+
+import (
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+)
+
+type Reloadable interface {
+ /**
+ * Reload states.
+ */
+ Reload()
+}
+
+type FileBasedSessionManager struct {
+ conf config.FileStoreConfig
+ DefaultSessionManager
+}
+
+func NewFileBasedSessionManager(conf config.FileStoreConfig) ISessionManager {
+ transactionStoreManager := &FileTransactionStoreManager{}
+ transactionStoreManager.InitFile(conf.FileDir)
+ sessionManager := DefaultSessionManager{
+ AbstractSessionManager: AbstractSessionManager{
+ TransactionStoreManager: transactionStoreManager,
+ Name: conf.FileDir,
+ },
+ SessionMap: make(map[string]*session.GlobalSession),
+ }
+ transactionStoreManager.SessionManager = &sessionManager
+ return &FileBasedSessionManager{
+ conf: conf,
+ DefaultSessionManager: sessionManager,
+ }
+}
+
+func (sessionManager *FileBasedSessionManager) Reload() {
+ sessionManager.restoreSessions()
+ sessionManager.washSessions()
+}
+
+func (sessionManager *FileBasedSessionManager) restoreSessions() {
+ unhandledBranchBuffer := make(map[int64]*session.BranchSession)
+ sessionManager.restoreSessionsToUnhandledBranchBuffer(true,unhandledBranchBuffer)
+ sessionManager.restoreSessionsToUnhandledBranchBuffer(false,unhandledBranchBuffer)
+ if len(unhandledBranchBuffer) > 0 {
+ for _,branchSession := range unhandledBranchBuffer {
+ found := sessionManager.SessionMap[branchSession.Xid]
+ if found == nil {
+ logging.Logger.Infof("GlobalSession Does Not Exists For BranchSession [%d/%s]",branchSession.BranchId,branchSession.Xid)
+ } else {
+ existingBranch := found.GetBranch(branchSession.BranchId)
+ if existingBranch == nil {
+ found.Add(branchSession)
+ } else {
+ existingBranch.Status = branchSession.Status
+ }
+ }
+ }
+ }
+}
+
+func (sessionManager *FileBasedSessionManager) restoreSessionsToUnhandledBranchBuffer(isHistory bool,unhandledBranchSessions map[int64]*session.BranchSession) {
+ transactionStoreManager, ok := sessionManager.TransactionStoreManager.(ReloadableStore)
+ if !ok {
+ return
+ }
+ for {
+ if transactionStoreManager.HasRemaining(isHistory) {
+ stores := transactionStoreManager.ReadWriteStore(sessionManager.conf.SessionReloadReadSize,isHistory)
+ sessionManager.restore(stores,unhandledBranchSessions)
+ } else {
+ break
+ }
+ }
+}
+
+func (sessionManager *FileBasedSessionManager) washSessions() {
+ if len(sessionManager.SessionMap) > 0 {
+ for _,globalSession := range sessionManager.SessionMap {
+ switch globalSession.Status {
+ case meta.GlobalStatusUnknown:
+ case meta.GlobalStatusCommitted:
+ case meta.GlobalStatusCommitFailed:
+ case meta.GlobalStatusRollbacked:
+ case meta.GlobalStatusRollbackFailed:
+ case meta.GlobalStatusTimeoutRollbacked:
+ case meta.GlobalStatusTimeoutRollbackFailed:
+ case meta.GlobalStatusFinished:
+ // Remove all sessions finished
+ delete(sessionManager.SessionMap, globalSession.Xid)
+ break
+ default:
+ break
+ }
+ }
+ }
+}
+
+func (sessionManager *FileBasedSessionManager) restore(stores []*TransactionWriteStore, unhandledBranchSessions map[int64]*session.BranchSession) {
+ maxRecoverId := util.UUID
+ for _,store := range stores {
+ logOperation := store.LogOperation
+ sessionStorable := store.SessionRequest
+ maxRecoverId = getMaxId(maxRecoverId, sessionStorable)
+ switch logOperation {
+ case LogOperationGlobalAdd:
+ case LogOperationGlobalUpdate:
+ {
+ globalSession := sessionStorable.(*session.GlobalSession)
+ if globalSession.TransactionId == int64(0) {
+ logging.Logger.Errorf("Restore globalSession from file failed, the transactionId is zero , xid:%s", globalSession.Xid)
+ break
+ }
+ foundGlobalSession := sessionManager.SessionMap[globalSession.Xid]
+ if foundGlobalSession == nil {
+ sessionManager.SessionMap[globalSession.Xid] = globalSession
+ } else {
+ foundGlobalSession.Status = globalSession.Status
+ }
+ break
+ }
+ case LogOperationGlobalRemove:
+ {
+ globalSession := sessionStorable.(*session.GlobalSession)
+ if globalSession.TransactionId == int64(0) {
+ logging.Logger.Errorf("Restore globalSession from file failed, the transactionId is zero , xid:%s", globalSession.Xid)
+ break
+ }
+ delete(sessionManager.SessionMap, globalSession.Xid)
+ break
+ }
+ case LogOperationBranchAdd:
+ case LogOperationBranchUpdate:
+ {
+ branchSession := sessionStorable.(*session.BranchSession)
+ if branchSession.TransactionId == int64(0) {
+ logging.Logger.Errorf("Restore branchSession from file failed, the transactionId is zero , xid:%s", branchSession.Xid)
+ break
+ }
+ foundGlobalSession := sessionManager.SessionMap[branchSession.Xid]
+ if foundGlobalSession == nil {
+ unhandledBranchSessions[branchSession.BranchId] = branchSession
+ } else {
+ existingBranch := foundGlobalSession.GetBranch(branchSession.BranchId)
+ if existingBranch == nil {
+ foundGlobalSession.Add(branchSession)
+ } else {
+ existingBranch.Status = branchSession.Status
+ }
+ }
+ break
+ }
+ case LogOperationBranchRemove:
+ {
+ branchSession := sessionStorable.(*session.BranchSession)
+ if branchSession.TransactionId == int64(0) {
+ logging.Logger.Errorf("Restore branchSession from file failed, the transactionId is zero , xid:%s", branchSession.Xid)
+ break
+ }
+ foundGlobalSession := sessionManager.SessionMap[branchSession.Xid]
+ if foundGlobalSession == nil {
+ logging.Logger.Infof("GlobalSession To Be Updated (Remove Branch) Does Not Exists [%d/%s]",branchSession.BranchId,branchSession.Xid)
+ } else {
+ existingBranch := foundGlobalSession.GetBranch(branchSession.BranchId)
+ if existingBranch == nil {
+ logging.Logger.Infof("BranchSession To Be Updated Does Not Exists [%d/%s]",branchSession.BranchId,branchSession.Xid)
+ } else {
+ foundGlobalSession.Remove(existingBranch)
+ }
+ }
+ break
+ }
+ default:
+ break
+ }
+ }
+ setMaxId(maxRecoverId)
+}
+
+func getMaxId(maxRecoverId int64, sessionStorable session.SessionStorable) int64 {
+ var currentId int64 = 0
+ var gs, ok1 = sessionStorable.(*session.GlobalSession)
+ if ok1 {
+ currentId = gs.TransactionId
+ }
+
+ var bs, ok2 = sessionStorable.(*session.BranchSession)
+ if ok2 {
+ currentId = bs.BranchId
+ }
+
+ if maxRecoverId > currentId {
+ return maxRecoverId
+ } else {
+ return currentId
+ }
+}
+
+func setMaxId(maxRecoverId int64) {
+ var currentId int64
+ // will be recover multi-thread later
+ for{
+ currentId = util.UUID
+ if currentId < maxRecoverId {
+ if util.SetUUID(currentId,maxRecoverId) {
+ break
+ }
+ }
+ break
+ }
+}
\ No newline at end of file
diff --git a/tc/holder/file_based_session_manager_test.go b/tc/holder/file_based_session_manager_test.go
new file mode 100644
index 0000000..21b108d
--- /dev/null
+++ b/tc/holder/file_based_session_manager_test.go
@@ -0,0 +1,132 @@
+package holder
+
+import (
+ "github.com/stretchr/testify/assert"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/model"
+ "testing"
+)
+
+func TestFileBasedSessionManager_AddGlobalSession(t *testing.T) {
+ gs := globalSessionProvider()
+
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+
+func TestFileBasedSessionManager_FindGlobalSession(t *testing.T) {
+ gs := globalSessionProvider()
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ expected := sessionManager.FindGlobalSession(gs.Xid)
+
+ assert.NotNil(t,expected)
+ assert.Equal(t,gs.TransactionId,expected.TransactionId)
+ assert.Equal(t,gs.ApplicationId,expected.ApplicationId)
+ assert.Equal(t,gs.TransactionServiceGroup,expected.TransactionServiceGroup)
+ assert.Equal(t,gs.TransactionName,expected.TransactionName)
+ assert.Equal(t,gs.Status,expected.Status)
+
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestFileBasedSessionManager_UpdateGlobalSessionStatus(t *testing.T) {
+ gs := globalSessionProvider()
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ gs.Status = meta.GlobalStatusFinished
+ sessionManager.UpdateGlobalSessionStatus(gs,meta.GlobalStatusFinished)
+
+ expected := sessionManager.FindGlobalSession(gs.Xid)
+ assert.NotNil(t,gs)
+ assert.Equal(t,meta.GlobalStatusFinished,expected.Status)
+
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestFileBasedSessionManager_RemoveGlobalSession(t *testing.T) {
+ gs := globalSessionProvider()
+
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.RemoveGlobalSession(gs)
+
+ expected := sessionManager.FindGlobalSession(gs.Xid)
+ assert.Nil(t,expected)
+}
+
+func TestFileBasedSessionManager_AddBranchSession(t *testing.T) {
+ gs := globalSessionProvider()
+ bs := branchSessionProvider(gs)
+
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.AddBranchSession(gs,bs)
+ sessionManager.RemoveBranchSession(gs,bs)
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestFileBasedSessionManager_UpdateBranchSessionStatus(t *testing.T) {
+ gs := globalSessionProvider()
+ bs := branchSessionProvider(gs)
+
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.AddBranchSession(gs,bs)
+ sessionManager.UpdateBranchSessionStatus(bs,meta.BranchStatusPhasetwoCommitted)
+ sessionManager.RemoveBranchSession(gs,bs)
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestFileBasedSessionManager_RemoveBranchSession(t *testing.T) {
+ gs := globalSessionProvider()
+ bs := branchSessionProvider(gs)
+
+ sessionManager := NewFileBasedSessionManager("root.data")
+ sessionManager.AddGlobalSession(gs)
+ sessionManager.AddBranchSession(gs,bs)
+ sessionManager.RemoveBranchSession(gs,bs)
+ sessionManager.RemoveGlobalSession(gs)
+}
+
+func TestFileBasedSessionManager_AllSessions(t *testing.T) {
+ gss := globalSessionsProvider()
+ sessionManager := NewFileBasedSessionManager("root.data")
+
+ for _,gs := range gss {
+ sessionManager.AddGlobalSession(gs)
+ }
+ allGs := sessionManager.AllSessions()
+ assert.NotNil(t,allGs)
+ assert.Equal(t,2,len(allGs))
+
+ for _,gs := range gss {
+ sessionManager.RemoveGlobalSession(gs)
+ }
+
+ allGs2 := sessionManager.AllSessions()
+ assert.Equal(t,0,len(allGs2))
+}
+
+func TestFileBasedSessionManager_FindGlobalSessionTest(t *testing.T) {
+ gss := globalSessionsProvider()
+ sessionManager := NewFileBasedSessionManager("root.data")
+
+ for _,gs := range gss {
+ sessionManager.AddGlobalSession(gs)
+ }
+ sessionCondition := model.SessionCondition{
+ OverTimeAliveMills: 30 * 24 * 3600,
+ }
+
+ expectedGlobalSessions := sessionManager.FindGlobalSessions(sessionCondition)
+
+ assert.NotNil(t,expectedGlobalSessions)
+ assert.Equal(t,2,len(expectedGlobalSessions))
+
+ for _,gs := range gss {
+ sessionManager.RemoveGlobalSession(gs)
+ }
+}
\ No newline at end of file
diff --git a/tc/holder/file_transaction_store_manager.go b/tc/holder/file_transaction_store_manager.go
new file mode 100644
index 0000000..9f9a69f
--- /dev/null
+++ b/tc/holder/file_transaction_store_manager.go
@@ -0,0 +1,232 @@
+package holder
+
+import (
+ "os"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/tc/model"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "vimagination.zapto.org/byteio"
+)
+
+var FileTrxNum int64 = 0
+var PerFileBlockSize int64 = 65535 * 8
+var HisDataFilenamePostfix = ".1"
+var MaxTrxTimeoutMills int64 = 30 * 60 * 1000
+
+type ReloadableStore interface {
+ /**
+ * Read write holder.
+ *
+ * @param readSize the read size
+ * @param isHistory the is history
+ * @return the list
+ */
+ ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore
+
+ /**
+ * Has remaining boolean.
+ *
+ * @param isHistory the is history
+ * @return the boolean
+ */
+ HasRemaining(isHistory bool) bool
+}
+
+type FileTransactionStoreManager struct {
+ SessionManager ISessionManager
+
+ currFullFileName string
+ hisFullFileName string
+ currFileChannel *os.File
+ LastModifiedTime int64
+ TrxStartTimeMills int64
+ sync.Mutex
+
+ recoverHisOffset int64
+ recoverCurrOffset int64
+}
+
+func (storeManager * FileTransactionStoreManager) InitFile(fullFileName string) {
+ storeManager.currFullFileName = fullFileName
+ storeManager.hisFullFileName = fullFileName + HisDataFilenamePostfix
+ storeManager.currFileChannel,_ = os.OpenFile(fullFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777)
+ storeManager.TrxStartTimeMills = int64(util.CurrentTimeMillis())
+}
+
+func (storeManager * FileTransactionStoreManager) writeDataFrame(data []byte) {
+ dataLength := uint32(len(data))
+ dataLengthBytes := [4]byte{
+ byte(dataLength >> 24),
+ byte(dataLength >> 16),
+ byte(dataLength >> 8),
+ byte(dataLength),
+ }
+ storeManager.currFileChannel.Write(dataLengthBytes[:4])
+ storeManager.currFileChannel.Write(data)
+}
+
+func (storeManager * FileTransactionStoreManager) WriteSession(logOperation LogOperation, session session.SessionStorable) bool {
+ storeManager.Lock()
+ defer storeManager.Unlock()
+ var curFileTrxNum int64 = 0
+ data, err := (&TransactionWriteStore{
+ SessionRequest: session,
+ LogOperation: logOperation,
+ }).Encode()
+ if err != nil {
+ logging.Logger.Info(err.Error())
+ return false
+ }
+ storeManager.writeDataFrame(data)
+ storeManager.LastModifiedTime = int64(util.CurrentTimeMillis())
+ curFileTrxNum = atomic.AddInt64(&FileTrxNum,1)
+ if curFileTrxNum %PerFileBlockSize == 0 &&
+ int64(util.CurrentTimeMillis()) - storeManager.TrxStartTimeMills > MaxTrxTimeoutMills {
+ storeManager.saveHistory()
+ }
+ return true
+}
+
+func (storeManager * FileTransactionStoreManager) ReadSession(xid string) *session.GlobalSession {
+ return nil
+}
+
+func (storeManager * FileTransactionStoreManager) ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession {
+ return nil
+}
+
+func (storeManager * FileTransactionStoreManager) ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession {
+ return nil
+}
+
+func (storeManager * FileTransactionStoreManager) Shutdown() {
+ storeManager.currFileChannel.Close()
+}
+
+func (storeManager * FileTransactionStoreManager) GetCurrentMaxSessionId() int64{
+ return int64(0)
+}
+
+func (storeManager * FileTransactionStoreManager) saveHistory() {
+ storeManager.findTimeoutAndSave()
+ os.Rename(storeManager.currFullFileName,storeManager.hisFullFileName)
+ storeManager.InitFile(storeManager.currFullFileName)
+}
+
+func (storeManager * FileTransactionStoreManager) findTimeoutAndSave() (bool,error) {
+ globalSessionsOverMaxTimeout := storeManager.SessionManager.FindGlobalSessions(model.SessionCondition{OverTimeAliveMills: MaxTrxTimeoutMills})
+
+ if globalSessionsOverMaxTimeout == nil {
+ return true,nil
+ }
+
+ for _, globalSession := range globalSessionsOverMaxTimeout {
+ globalWriteStore := &TransactionWriteStore{
+ SessionRequest: globalSession,
+ LogOperation: LogOperationGlobalAdd,
+ }
+ data,err := globalWriteStore.Encode()
+ if err != nil {
+ return false,err
+ }
+ storeManager.writeDataFrame(data)
+
+ branchSessIonsOverMaXTimeout := globalSession.GetSortedBranches()
+ if len(branchSessIonsOverMaXTimeout) > 0 {
+ for _,branchSession := range branchSessIonsOverMaXTimeout {
+ branchWriteStore := &TransactionWriteStore{
+ SessionRequest: branchSession,
+ LogOperation: LogOperationBranchAdd,
+ }
+ data,err := branchWriteStore.Encode()
+ if err != nil {
+ return false,err
+ }
+ storeManager.writeDataFrame(data)
+ }
+ }
+ }
+ return true,nil
+}
+
+func (storeManager * FileTransactionStoreManager) ReadWriteStore(readSize int, isHistory bool) []*TransactionWriteStore {
+ var (
+ file *os.File
+ currentOffset int64
+ )
+ if isHistory {
+ file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777)
+ currentOffset = storeManager.recoverHisOffset
+ } else {
+ file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777)
+ currentOffset = storeManager.recoverCurrOffset
+ }
+
+ return storeManager.parseDataFile(file,readSize,currentOffset)
+}
+
+func (storeManager * FileTransactionStoreManager) HasRemaining(isHistory bool) bool {
+ var (
+ file *os.File
+ currentOffset int64
+ )
+ if isHistory {
+ file, _ = os.OpenFile(storeManager.hisFullFileName, os.O_RDWR|os.O_CREATE, 0777)
+ currentOffset = storeManager.recoverHisOffset
+ } else {
+ file, _ = os.OpenFile(storeManager.currFullFileName, os.O_RDWR|os.O_CREATE, 0777)
+ currentOffset = storeManager.recoverCurrOffset
+ }
+ defer file.Close()
+
+ fi,_ := file.Stat()
+ return currentOffset < fi.Size()
+}
+
+func (storeManager * FileTransactionStoreManager) parseDataFile(file *os.File, readSize int, currentOffset int64) []*TransactionWriteStore {
+ defer file.Close()
+ var result = make([]*TransactionWriteStore,0)
+ fi,_ := file.Stat()
+ fileSize := fi.Size()
+ reader := byteio.BigEndianReader{Reader:file}
+ offset := currentOffset
+ for {
+ if offset >= fileSize {
+ break
+ }
+ file.Seek(offset, 0)
+ dataLength, n, _ := reader.ReadUint32()
+ if n < 4 {
+ break
+ }
+
+ data := make([]byte, int(dataLength))
+ length, _ := reader.Read(data)
+ offset += int64(length + 4)
+
+ if length == int(dataLength) {
+ st := &TransactionWriteStore{}
+ st.Decode(data)
+ result = append(result, st)
+ if len(result) == readSize {
+ break
+ }
+ } else if length == 0 {
+ break
+ }
+ }
+ if isHisFile(fi.Name()) {
+ storeManager.recoverHisOffset = offset
+ } else {
+ storeManager.recoverCurrOffset = offset
+ }
+ return result
+}
+
+func isHisFile(path string) bool {
+ return strings.HasSuffix(path,HisDataFilenamePostfix)
+}
\ No newline at end of file
diff --git a/tc/holder/session_holder.go b/tc/holder/session_holder.go
new file mode 100644
index 0000000..2652289
--- /dev/null
+++ b/tc/holder/session_holder.go
@@ -0,0 +1,93 @@
+package holder
+
+import (
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/tc/lock"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+)
+
+type SessionHolder struct {
+ RootSessionManager ISessionManager
+ AsyncCommittingSessionManager ISessionManager
+ RetryCommittingSessionManager ISessionManager
+ RetryRollbackingSessionManager ISessionManager
+}
+
+var sessionHolder SessionHolder
+
+func init() {
+ sessionHolder = SessionHolder{
+ RootSessionManager: NewFileBasedSessionManager(config.GetDefaultFileStoreConfig()),
+ AsyncCommittingSessionManager: NewDefaultSessionManager("default"),
+ RetryCommittingSessionManager: NewDefaultSessionManager("default"),
+ RetryRollbackingSessionManager: NewDefaultSessionManager("default"),
+ }
+
+ sessionHolder.reload()
+}
+
+func GetSessionHolder() SessionHolder {
+ return sessionHolder
+}
+
+func (sessionHolder SessionHolder) FindGlobalSession(xid string) *session.GlobalSession {
+ return sessionHolder.FindGlobalSessionWithBranchSessions(xid, true)
+}
+
+func (sessionHolder SessionHolder) FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession {
+ return sessionHolder.RootSessionManager.FindGlobalSessionWithBranchSessions(xid, withBranchSessions)
+}
+
+func (sessionHolder SessionHolder) reload() {
+ sessionManager, reloadable := sessionHolder.RootSessionManager.(Reloadable)
+ if reloadable {
+ sessionManager.Reload()
+
+ reloadedSessions := sessionHolder.RootSessionManager.AllSessions()
+ if reloadedSessions != nil && len(reloadedSessions) > 0 {
+ for _,globalSession := range reloadedSessions {
+ switch globalSession.Status {
+ case meta.GlobalStatusUnknown:
+ case meta.GlobalStatusCommitted:
+ case meta.GlobalStatusCommitFailed:
+ case meta.GlobalStatusRollbacked:
+ case meta.GlobalStatusRollbackFailed:
+ case meta.GlobalStatusTimeoutRollbacked:
+ case meta.GlobalStatusTimeoutRollbackFailed:
+ case meta.GlobalStatusFinished:
+ logging.Logger.Errorf("Reloaded Session should NOT be %s",globalSession.Status.String())
+ break
+ case meta.GlobalStatusAsyncCommitting:
+ sessionHolder.AsyncCommittingSessionManager.AddGlobalSession(globalSession)
+ break
+ default:
+ branchSessions := globalSession.GetSortedBranches()
+ for _,branchSession := range branchSessions {
+ lock.GetLockManager().AcquireLock(branchSession)
+ }
+ switch globalSession.Status {
+ case meta.GlobalStatusCommitting:
+ case meta.GlobalStatusCommitRetrying:
+ sessionHolder.RetryCommittingSessionManager.AddGlobalSession(globalSession)
+ break
+ case meta.GlobalStatusRollbacking:
+ case meta.GlobalStatusRollbackRetrying:
+ case meta.GlobalStatusTimeoutRollbacking:
+ case meta.GlobalStatusTimeoutRollbackRetrying:
+ sessionHolder.RetryRollbackingSessionManager.AddGlobalSession(globalSession)
+ break
+ case meta.GlobalStatusBegin:
+ globalSession.SetActive(true)
+ break
+ default:
+ logging.Logger.Errorf("NOT properly handled %s",globalSession.Status)
+ break
+ }
+ break
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tc/holder/session_manager.go b/tc/holder/session_manager.go
new file mode 100644
index 0000000..b3f301d
--- /dev/null
+++ b/tc/holder/session_manager.go
@@ -0,0 +1,181 @@
+package holder
+
+import (
+ "errors"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/model"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+)
+
+type ISessionManager interface {
+ /**
+ * Add global session.
+ *
+ * @param session the session
+ * @throws TransactionException the transaction exception
+ */
+ AddGlobalSession(session *session.GlobalSession) error
+
+ /**
+ * Find global session global session.
+ *
+ * @param xid the xid
+ * @return the global session
+ */
+ FindGlobalSession(xid string) *session.GlobalSession
+
+ /**
+ * Find global session global session.
+ *
+ * @param xid the xid
+ * @param withBranchSessions the withBranchSessions
+ * @return the global session
+ */
+ FindGlobalSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession
+
+ /**
+ * Update global session status.
+ *
+ * @param session the session
+ * @param status the status
+ * @throws TransactionException the transaction exception
+ */
+ UpdateGlobalSessionStatus(session *session.GlobalSession, status meta.GlobalStatus) error
+
+ /**
+ * Remove global session.
+ *
+ * @param session the session
+ * @throws TransactionException the transaction exception
+ */
+ RemoveGlobalSession(session *session.GlobalSession) error
+
+ /**
+ * Add branch session.
+ *
+ * @param globalSession the global session
+ * @param session the session
+ * @throws TransactionException the transaction exception
+ */
+ AddBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error
+
+ /**
+ * Update branch session status.
+ *
+ * @param session the session
+ * @param status the status
+ * @throws TransactionException the transaction exception
+ */
+ UpdateBranchSessionStatus(session *session.BranchSession, status meta.BranchStatus) error
+
+ /**
+ * Remove branch session.
+ *
+ * @param globalSession the global session
+ * @param session the session
+ * @throws TransactionException the transaction exception
+ */
+ RemoveBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error
+
+ /**
+ * All sessions collection.
+ *
+ * @return the collection
+ */
+ AllSessions() []*session.GlobalSession
+
+ /**
+ * Find global sessions list.
+ *
+ * @param condition the condition
+ * @return the list
+ */
+ FindGlobalSessions(condition model.SessionCondition) []*session.GlobalSession
+}
+
+type AbstractSessionManager struct {
+ TransactionStoreManager ITransactionStoreManager
+ Name string
+}
+
+func (sessionManager *AbstractSessionManager) AddGlobalSession(session *session.GlobalSession) error{
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalAdd.String())
+ sessionManager.writeSession(LogOperationGlobalAdd,session)
+ return nil
+}
+
+func (sessionManager *AbstractSessionManager) UpdateGlobalSessionStatus(session *session.GlobalSession, status meta.GlobalStatus) error {
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalUpdate.String())
+ sessionManager.writeSession(LogOperationGlobalUpdate,session)
+ return nil
+}
+
+func (sessionManager *AbstractSessionManager) RemoveGlobalSession(session *session.GlobalSession) error{
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationGlobalRemove.String())
+ sessionManager.writeSession(LogOperationGlobalRemove,session)
+ return nil
+}
+
+func (sessionManager *AbstractSessionManager) AddBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error{
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchAdd.String())
+ sessionManager.writeSession(LogOperationBranchAdd,session)
+ return nil
+}
+
+func (sessionManager *AbstractSessionManager) UpdateBranchSessionStatus(session *session.BranchSession, status meta.BranchStatus) error{
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchUpdate.String())
+ sessionManager.writeSession(LogOperationBranchUpdate,session)
+ return nil
+}
+
+func (sessionManager *AbstractSessionManager) RemoveBranchSession(globalSession *session.GlobalSession, session *session.BranchSession) error{
+ logging.Logger.Debugf("MANAGER[%s] SESSION[%v] %s",sessionManager.Name, session, LogOperationBranchRemove.String())
+ sessionManager.writeSession(LogOperationBranchRemove,session)
+ return nil
+}
+
+
+func (sessionManager *AbstractSessionManager) writeSession(logOperation LogOperation, sessionStorable session.SessionStorable) error {
+ result := sessionManager.TransactionStoreManager.WriteSession(logOperation,sessionStorable)
+ if !result {
+ if logOperation == LogOperationGlobalAdd {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to holder global session",
+ }
+ }
+ if logOperation == LogOperationGlobalUpdate {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to update global session",
+ }
+ }
+ if logOperation == LogOperationGlobalRemove {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to remove global session",
+ }
+ }
+ if logOperation == LogOperationBranchAdd {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to holder branch session",
+ }
+ }
+ if logOperation == LogOperationBranchUpdate {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to update branch session",
+ }
+ }
+ if logOperation == LogOperationBranchRemove {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeFailedWriteSession,
+ Message: "Fail to remove branch session",
+ }
+ }
+ return errors.New("Unknown LogOperation:"+logOperation.String())
+ }
+ return nil
+}
\ No newline at end of file
diff --git a/tc/holder/transaction_store_manager.go b/tc/holder/transaction_store_manager.go
new file mode 100644
index 0000000..a0a2a44
--- /dev/null
+++ b/tc/holder/transaction_store_manager.go
@@ -0,0 +1,135 @@
+package holder
+
+import (
+ "fmt"
+ "github.com/dk-lockdown/seata-golang/tc/model"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+)
+
+type LogOperation byte
+
+const (
+ LogOperationGlobalAdd LogOperation = iota
+ /**
+ * Global update log operation.
+ */
+ LogOperationGlobalUpdate
+ /**
+ * Global remove log operation.
+ */
+ LogOperationGlobalRemove
+ /**
+ * Branch add log operation.
+ */
+ LogOperationBranchAdd
+ /**
+ * Branch update log operation.
+ */
+ LogOperationBranchUpdate
+ /**
+ * Branch remove log operation.
+ */
+ LogOperationBranchRemove
+)
+
+func (t LogOperation) String() string {
+ switch t {
+ case LogOperationGlobalAdd:
+ return "GlobalAdd"
+ case LogOperationGlobalUpdate:
+ return "GlobalUpdate"
+ case LogOperationGlobalRemove:
+ return "GlobalRemove"
+ case LogOperationBranchAdd:
+ return "BranchAdd"
+ case LogOperationBranchUpdate:
+ return "BranchUpdate"
+ case LogOperationBranchRemove:
+ return "BranchRemove"
+ default:
+ return fmt.Sprintf("%d", t)
+ }
+}
+
+type ITransactionStoreManager interface {
+ /**
+ * Write session boolean.
+ *
+ * @param logOperation the log operation
+ * @param session the session
+ * @return the boolean
+ */
+ WriteSession(logOperation LogOperation, session session.SessionStorable) bool
+
+
+ /**
+ * Read global session global session.
+ *
+ * @param xid the xid
+ * @return the global session
+ */
+ ReadSession(xid string) *session.GlobalSession
+
+ /**
+ * Read session global session.
+ *
+ * @param xid the xid
+ * @param withBranchSessions the withBranchSessions
+ * @return the global session
+ */
+ ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession
+
+ /**
+ * Read session by status list.
+ *
+ * @param sessionCondition the session condition
+ * @return the list
+ */
+ ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession
+
+ /**
+ * Shutdown.
+ */
+ Shutdown()
+
+ /**
+ * Gets current max session id.
+ *
+ * @return the current max session id
+ */
+ GetCurrentMaxSessionId() int64
+}
+
+type AbstractTransactionStoreManager struct {
+
+}
+
+func (transactionStoreManager *AbstractTransactionStoreManager) WriteSession(logOperation LogOperation, session session.SessionStorable) bool {
+ return true
+}
+
+
+
+func (transactionStoreManager *AbstractTransactionStoreManager) ReadSession(xid string) *session.GlobalSession {
+ return nil
+}
+
+
+func (transactionStoreManager *AbstractTransactionStoreManager) ReadSessionWithBranchSessions(xid string, withBranchSessions bool) *session.GlobalSession {
+ return nil
+}
+
+func (transactionStoreManager *AbstractTransactionStoreManager) ReadSessionWithSessionCondition(sessionCondition model.SessionCondition) []*session.GlobalSession {
+ return nil
+}
+
+func (transactionStoreManager *AbstractTransactionStoreManager) Shutdown() {
+
+}
+
+
+func (transactionStoreManager *AbstractTransactionStoreManager) GetCurrentMaxSessionId() int64 {
+ return 0
+}
+
+
diff --git a/tc/holder/transaction_write_store.go b/tc/holder/transaction_write_store.go
new file mode 100644
index 0000000..81d8d4f
--- /dev/null
+++ b/tc/holder/transaction_write_store.go
@@ -0,0 +1,53 @@
+package holder
+
+import (
+ "github.com/pkg/errors"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+)
+
+type TransactionWriteStore struct {
+ SessionRequest session.SessionStorable
+ LogOperation LogOperation
+}
+
+func (transactionWriteStore *TransactionWriteStore) Encode() ([]byte, error){
+ bySessionRequest,err := transactionWriteStore.SessionRequest.Encode()
+ if err != nil {
+ return nil,err
+ }
+ byOpCode := transactionWriteStore.LogOperation
+
+ var result = make([]byte,0)
+ result = append(result,bySessionRequest...)
+ result = append(result,byte(byOpCode))
+ return result,nil
+}
+
+func (transactionWriteStore *TransactionWriteStore) Decode(src []byte) {
+ bySessionRequest := src[:len(src)-1]
+ byOpCode := src[len(src)-1:]
+
+ transactionWriteStore.LogOperation = LogOperation(byOpCode[0])
+ sessionRequest, _ := transactionWriteStore.getSessionInstanceByOperation()
+ sessionRequest.Decode(bySessionRequest)
+ transactionWriteStore.SessionRequest = sessionRequest
+}
+
+func (transactionWriteStore *TransactionWriteStore) getSessionInstanceByOperation() (session.SessionStorable,error) {
+ var sessionStorable session.SessionStorable
+ switch transactionWriteStore.LogOperation {
+ case LogOperationGlobalAdd:
+ case LogOperationGlobalUpdate:
+ case LogOperationGlobalRemove:
+ sessionStorable = session.NewGlobalSession()
+ break
+ case LogOperationBranchAdd:
+ case LogOperationBranchUpdate:
+ case LogOperationBranchRemove:
+ sessionStorable = session.NewBranchSession()
+ break
+ default:
+ return nil,errors.New("incorrect logOperation.")
+ }
+ return sessionStorable,nil
+}
\ No newline at end of file
diff --git a/tc/lock/lock_manager.go b/tc/lock/lock_manager.go
new file mode 100644
index 0000000..145efbd
--- /dev/null
+++ b/tc/lock/lock_manager.go
@@ -0,0 +1,56 @@
+package lock
+
+import (
+ "github.com/dk-lockdown/seata-golang/tc/session"
+)
+
+type ILockManager interface {
+ /**
+ * Acquire lock boolean.
+ *
+ * @param branchSession the branch session
+ * @return the boolean
+ * @throws TransactionException the transaction exception
+ */
+ AcquireLock(branchSession *session.BranchSession) (bool, error)
+
+ /**
+ * Un lock boolean.
+ *
+ * @param branchSession the branch session
+ * @return the boolean
+ * @throws TransactionException the transaction exception
+ */
+ ReleaseLock(branchSession *session.BranchSession) (bool, error)
+
+ /**
+ * GlobalSession 是没有锁的,所有的锁都在 BranchSession 上,因为 BranchSession 才
+ * 持有资源,释放 GlobalSession 锁是指释放它所有的 BranchSession 上的锁
+ * Un lock boolean.
+ *
+ * @param globalSession the global session
+ * @return the boolean
+ * @throws TransactionException the transaction exception
+ */
+ ReleaseGlobalSessionLock(globalSession *session.GlobalSession) (bool, error)
+
+ /**
+ * Is lockable boolean.
+ *
+ * @param xid the xid
+ * @param resourceId the resource id
+ * @param lockKey the lock key
+ * @return the boolean
+ * @throws TransactionException the transaction exception
+ */
+ IsLockable(xid string, resourceId string, lockKey string) bool
+
+ /**
+ * Clean all locks.
+ *
+ * @throws TransactionException the transaction exception
+ */
+ CleanAllLocks()
+
+ GetLockKeyCount() int64
+}
\ No newline at end of file
diff --git a/tc/lock/lock_manager_test.go b/tc/lock/lock_manager_test.go
new file mode 100644
index 0000000..c919709
--- /dev/null
+++ b/tc/lock/lock_manager_test.go
@@ -0,0 +1,152 @@
+package lock
+
+import (
+ "github.com/stretchr/testify/assert"
+ "github.com/dk-lockdown/seata-golang/common"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+ "sync"
+ "testing"
+)
+
+func TestLockManager_AcquireLock(t *testing.T) {
+ bs := branchSessionProvider()
+ ok,err := GetLockManager().AcquireLock(bs)
+ assert.Equal(t,ok,true)
+ assert.Equal(t,err,nil)
+}
+
+func TestLockManager_IsLockable(t *testing.T) {
+ transId := util.GeneratorUUID()
+ ok := GetLockManager().IsLockable(common.XID.GenerateXID(transId),"tb_1","tb_1:13")
+ assert.Equal(t,ok,true)
+}
+
+func TestLockManager_AcquireLock_Fail(t *testing.T) {
+ sessions := branchSessionsProvider()
+ result1,err1 := GetLockManager().AcquireLock(sessions[0])
+ result2,err2 := GetLockManager().AcquireLock(sessions[1])
+ assert.True(t,result1)
+ assert.Equal(t,err1,nil)
+ assert.False(t,result2)
+ assert.Equal(t,err2,nil)
+}
+
+func TestLockManager_AcquireLock_DeadLock(t *testing.T) {
+ sessions := deadlockBranchSessionsProvider()
+ defer func() {
+ GetLockManager().ReleaseLock(sessions[0])
+ GetLockManager().ReleaseLock(sessions[1])
+ }()
+
+ wg := sync.WaitGroup{}
+ wg.Add(2)
+ go func(session *session.BranchSession) {
+ defer wg.Done()
+ result, err := GetLockManager().AcquireLock(session)
+ logging.Logger.Infof("1: %v %v",result,err)
+ }(sessions[0])
+
+ go func(session *session.BranchSession) {
+ defer wg.Done()
+ result, err := GetLockManager().AcquireLock(session)
+ logging.Logger.Infof("2: %v %v",result,err)
+ }(sessions[1])
+ wg.Wait()
+ assert.True(t,true)
+}
+
+func TestLockManager_IsLockable2(t *testing.T) {
+ bs := branchSessionProvider()
+ bs.SetLockKey("t:4")
+ result1 := GetLockManager().IsLockable(bs.Xid,bs.ResourceId,bs.LockKey)
+ assert.True(t,result1)
+ GetLockManager().AcquireLock(bs)
+ bs.SetTransactionId(util.GeneratorUUID())
+ result2 := GetLockManager().IsLockable(bs.Xid,bs.ResourceId,bs.LockKey)
+ assert.False(t,result2)
+}
+
+func TestLockManager_AcquireLock_SessionHolder(t *testing.T) {
+ sessions := duplicatePkBranchSessionsProvider()
+ result1, _ := GetLockManager().AcquireLock(sessions[0])
+ assert.True(t,result1)
+ assert.Equal(t,int64(4),GetLockManager().GetLockKeyCount())
+ result2, _ := GetLockManager().ReleaseLock(sessions[0])
+ assert.True(t,result2)
+ assert.Equal(t,int64(0),GetLockManager().GetLockKeyCount())
+
+ result3, _ := GetLockManager().AcquireLock(sessions[1])
+ assert.True(t,result3)
+ assert.Equal(t,int64(4),GetLockManager().GetLockKeyCount())
+ result4, _ := GetLockManager().ReleaseLock(sessions[1])
+ assert.True(t,result4)
+ assert.Equal(t,int64(0),GetLockManager().GetLockKeyCount())
+}
+
+func deadlockBranchSessionsProvider() []*session.BranchSession {
+ return baseBranchSessionsProvider("tb_2", "t:1,2,3,4,5", "t:5,4,3,2,1")
+}
+
+func duplicatePkBranchSessionsProvider() []*session.BranchSession {
+ return baseBranchSessionsProvider("tb_2", "t:1,2;t1:1;t2:2", "t:1,2;t1:1;t2:2")
+}
+
+func branchSessionsProvider() []*session.BranchSession {
+ return baseBranchSessionsProvider("tb_1", "t:1,2", "t:1,2")
+}
+
+func baseBranchSessionsProvider(resourceId string, lockKey1 string, lockKey2 string) []*session.BranchSession {
+ var branchSessions = make([]*session.BranchSession,0)
+ transId := util.GeneratorUUID()
+ transId2 := util.GeneratorUUID()
+ bs := session.NewBranchSession().
+ SetXid(common.XID.GenerateXID(transId)).
+ SetTransactionId(transId).
+ SetBranchId(1).
+ SetResourceGroupId("my_test_tx_group").
+ SetResourceId(resourceId).
+ SetLockKey(lockKey1).
+ SetBranchType(meta.BranchTypeAT).
+ SetStatus(meta.BranchStatusUnknown).
+ SetClientId("c1").
+ SetApplicationData([]byte("{\"data\":\"test\"}"))
+
+ bs1 := session.NewBranchSession().
+ SetXid(common.XID.GenerateXID(transId2)).
+ SetTransactionId(transId2).
+ SetBranchId(1).
+ SetResourceGroupId("my_test_tx_group").
+ SetResourceId(resourceId).
+ SetLockKey(lockKey2).
+ SetBranchType(meta.BranchTypeAT).
+ SetStatus(meta.BranchStatusUnknown).
+ SetClientId("c1").
+ SetApplicationData([]byte("{\"data\":\"test\"}"))
+
+ branchSessions = append(branchSessions,bs)
+ branchSessions = append(branchSessions,bs1)
+ return branchSessions
+}
+
+func branchSessionProvider() *session.BranchSession {
+ common.XID.IpAddress="127.0.0.1"
+ common.XID.Port=9876
+
+ transId := util.GeneratorUUID()
+ bs := session.NewBranchSession().
+ SetXid(common.XID.GenerateXID(transId)).
+ SetTransactionId(transId).
+ SetBranchId(1).
+ SetResourceGroupId("my_test_tx_group").
+ SetResourceId("tb_1").
+ SetLockKey("tb_1:13").
+ SetBranchType(meta.BranchTypeAT).
+ SetStatus(meta.BranchStatusUnknown).
+ SetClientId("c1").
+ SetApplicationData([]byte("{\"data\":\"test\"}"))
+
+ return bs
+}
\ No newline at end of file
diff --git a/tc/lock/locker.go b/tc/lock/locker.go
new file mode 100644
index 0000000..d0eb7ca
--- /dev/null
+++ b/tc/lock/locker.go
@@ -0,0 +1,16 @@
+package lock
+
+import "sync"
+
+var lockManager ILockManager
+
+func init() {
+ lockManager = &MemoryLocker{
+ LockMap: &sync.Map{},
+ BucketHolder: &sync.Map{},
+ }
+}
+
+func GetLockManager() ILockManager {
+ return lockManager
+}
\ No newline at end of file
diff --git a/tc/lock/memory_lock.go b/tc/lock/memory_lock.go
new file mode 100644
index 0000000..f423bd5
--- /dev/null
+++ b/tc/lock/memory_lock.go
@@ -0,0 +1,188 @@
+package lock
+
+import (
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/model"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+ "github.com/pkg/errors"
+ "strconv"
+ "sync"
+ "sync/atomic"
+)
+
+const BucketPerTable = 128
+
+type MemoryLocker struct {
+ LockMap *sync.Map
+ // 高流量下,锁资源越多,BucketHolder 的性能越下降
+ BucketHolder *sync.Map
+
+ LockKeyCount int64
+}
+
+
+func (ml *MemoryLocker) AcquireLock(branchSession *session.BranchSession) (bool, error) {
+ if branchSession == nil {
+ logging.Logger.Errorf("branchSession can't be null for memory/file locker.")
+ return false, errors.New("branchSession can't be null for memory/file locker.")
+ }
+
+ lockKey := branchSession.LockKey
+ if lockKey == "" {
+ return true,nil
+ }
+
+ locks := collectRowLocksByBranchSession(branchSession)
+ if locks == nil { return true,nil }
+ return ml.acquireLockByRowLocks(branchSession,locks)
+}
+
+
+func (ml *MemoryLocker) ReleaseLock(branchSession *session.BranchSession) (bool, error) {
+ if branchSession == nil {
+ logging.Logger.Info("branchSession can't be null for memory/file locker.")
+ return false,errors.New("branchSession can't be null for memory/file locker")
+ }
+
+ locks := collectRowLocksByBranchSession(branchSession)
+ return ml.releaseLockByRowLocks(branchSession,locks)
+}
+
+func (ml *MemoryLocker) ReleaseGlobalSessionLock(globalSession *session.GlobalSession) (bool, error) {
+ branchSessions := globalSession.GetSortedBranches()
+ releaseLockResult := true
+ for _,branchSession := range branchSessions {
+ ok, err := ml.ReleaseLock(branchSession)
+ if err != nil {
+ return ok,err
+ }
+ if !ok { releaseLockResult = false }
+
+ }
+ return releaseLockResult, nil
+}
+
+func (ml *MemoryLocker) IsLockable(xid string, resourceId string, lockKey string) bool {
+ locks := collectRowLocksByLockKeyResourceIdXid(lockKey, resourceId, xid)
+ return ml.isLockableByRowLocks(locks)
+}
+
+func (ml *MemoryLocker) CleanAllLocks() {
+ ml.LockMap = &sync.Map{}
+ ml.BucketHolder = &sync.Map{}
+ ml.LockKeyCount = 0
+}
+
+func (ml *MemoryLocker) GetLockKeyCount() int64 {
+ return ml.LockKeyCount
+}
+
+// AcquireLock 申请锁资源,resourceId -> tableName -> bucketId -> pk -> transactionId
+func (ml *MemoryLocker) acquireLockByRowLocks(branchSession *session.BranchSession,rowLocks []*RowLock) (bool, error) {
+ if rowLocks == nil { return true, nil }
+
+ resourceId := branchSession.ResourceId
+ transactionId := branchSession.TransactionId
+
+ dbLockMap,_ := ml.LockMap.LoadOrStore(resourceId,&sync.Map{})
+
+ cDbLockMap := dbLockMap.(*sync.Map)
+ for _, rowLock := range rowLocks {
+ tableLockMap,_ := cDbLockMap.LoadOrStore(rowLock.TableName,&sync.Map{})
+
+ cTableLockMap := tableLockMap.(*sync.Map)
+
+ bucketId := util.String(rowLock.Pk) % BucketPerTable
+ bucketKey := strconv.Itoa(bucketId)
+ bucketLockMap,_ := cTableLockMap.LoadOrStore(bucketKey,&sync.Map{})
+
+ cBucketLockMap := bucketLockMap.(*sync.Map)
+
+ previousLockTransactionId,loaded := cBucketLockMap.LoadOrStore(rowLock.Pk, transactionId)
+ if !loaded {
+
+ //No existing rowLock, and now locked by myself
+ keysInHolder,_ := ml.BucketHolder.LoadOrStore(cBucketLockMap, model.NewSet())
+
+ sKeysInHolder := keysInHolder.(*model.Set)
+ sKeysInHolder.Add(rowLock.Pk)
+
+ atomic.AddInt64(&ml.LockKeyCount,1)
+ } else if previousLockTransactionId == transactionId {
+ // Locked by me before
+ continue
+ } else {
+ logging.Logger.Infof("Global rowLock on [%s:%s] is holding by %d", rowLock.TableName, rowLock.Pk,previousLockTransactionId)
+ // branchSession unlock
+ _,err := ml.ReleaseLock(branchSession)
+ return false,err
+ }
+ }
+
+ return true, nil
+}
+
+func (ml *MemoryLocker) releaseLockByRowLocks(branchSession *session.BranchSession,rowLocks []*RowLock) (bool,error) {
+ if rowLocks == nil { return false, nil }
+
+ releaseLock := func (key, value interface{}) bool {
+ cBucketLockMap := key.(*sync.Map)
+ keys := value.(*model.Set)
+
+ for _, key := range keys.List() {
+ transId, ok := cBucketLockMap.Load(key)
+ if ok && transId == branchSession.TransactionId {
+ cBucketLockMap.Delete(key)
+ // keys.List() 是一个新的 slice,移除 key 并不会导致错误发生
+ keys.Remove(key)
+ atomic.AddInt64(&ml.LockKeyCount,-1)
+ }
+ }
+ return true
+ }
+
+ ml.BucketHolder.Range(releaseLock)
+
+ return true, nil
+}
+
+func (ml *MemoryLocker) isLockableByRowLocks(rowLocks []*RowLock) bool {
+ if rowLocks == nil { return true }
+
+ resourceId := rowLocks[0].ResourceId
+ transactionId := rowLocks[0].TransactionId
+
+ dbLockMap, ok := ml.LockMap.Load(resourceId)
+ if !ok {
+ return true
+ }
+
+ cDbLockMap := dbLockMap.(*sync.Map)
+ for _, rowLock := range rowLocks {
+ tableLockMap,ok := cDbLockMap.Load(rowLock.TableName)
+ if !ok {
+ continue
+ }
+ cTableLockMap := tableLockMap.(*sync.Map)
+
+ bucketId := util.String(rowLock.Pk) % BucketPerTable
+ bucketKey := strconv.Itoa(bucketId)
+ bucketLockMap,ok := cTableLockMap.Load(bucketKey)
+ if !ok {
+ continue
+ }
+ cBucketLockMap := bucketLockMap.(*sync.Map)
+
+ previousLockTransactionId,ok := cBucketLockMap.Load(rowLock.Pk)
+ if !ok || previousLockTransactionId == transactionId {
+ // Locked by me before
+ continue
+ } else {
+ logging.Logger.Infof("Global rowLock on [%s:%s] is holding by %d", rowLock.TableName, rowLock.Pk,previousLockTransactionId)
+ return false
+ }
+ }
+
+ return true
+}
\ No newline at end of file
diff --git a/tc/lock/row_lock.go b/tc/lock/row_lock.go
new file mode 100644
index 0000000..4059e05
--- /dev/null
+++ b/tc/lock/row_lock.go
@@ -0,0 +1,76 @@
+package lock
+
+import (
+ "github.com/dk-lockdown/seata-golang/common"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "strings"
+)
+
+type RowLock struct {
+ Xid string
+
+ TransactionId int64
+
+ BranchId int64
+
+ ResourceId string
+
+ TableName string
+
+ Pk string
+
+ RowKey string
+
+ Feature string
+}
+
+
+func collectRowLocksByBranchSession(branchSession *session.BranchSession) []*RowLock {
+ if branchSession == nil || branchSession.LockKey == "" {
+ return nil
+ }
+ return collectRowLocks(branchSession.LockKey,branchSession.ResourceId,branchSession.Xid,branchSession.TransactionId,branchSession.BranchId)
+}
+
+func collectRowLocksByLockKeyResourceIdXid(lockKey string,
+ resourceId string,
+ xid string) []*RowLock {
+
+ return collectRowLocks(lockKey,resourceId,xid,common.XID.GetTransactionId(xid),0)
+}
+
+func collectRowLocks(lockKey string,
+ resourceId string,
+ xid string,
+ transactionId int64,
+ branchId int64) []*RowLock {
+ var locks = make([]*RowLock,0)
+ tableGroupedLockKeys := strings.Split(lockKey,";")
+ for _, tableGroupedLockKey := range tableGroupedLockKeys {
+ idx := strings.Index(tableGroupedLockKey,":")
+ if idx < 0 { return nil }
+
+ tableName := tableGroupedLockKey[0:idx]
+ mergedPKs := tableGroupedLockKey[idx+1:]
+
+ if mergedPKs == "" { return nil }
+
+ pks := strings.Split(mergedPKs,",")
+ if len(pks) == 0 { return nil }
+
+ for _,pk := range pks {
+ if pk != "" {
+ rowLock := &RowLock{
+ Xid: xid,
+ TransactionId: transactionId,
+ BranchId: branchId,
+ ResourceId: resourceId,
+ TableName: tableName,
+ Pk: pk,
+ }
+ locks = append(locks,rowLock)
+ }
+ }
+ }
+ return locks
+}
\ No newline at end of file
diff --git a/tc/model/session_condition.go b/tc/model/session_condition.go
new file mode 100644
index 0000000..78e7390
--- /dev/null
+++ b/tc/model/session_condition.go
@@ -0,0 +1,11 @@
+package model
+
+import "github.com/dk-lockdown/seata-golang/meta"
+
+type SessionCondition struct{
+ TransactionId int64
+ Xid string
+ Status meta.GlobalStatus
+ Statuses []meta.GlobalStatus
+ OverTimeAliveMills int64
+}
\ No newline at end of file
diff --git a/tc/server/default_coordinator.go b/tc/server/default_coordinator.go
new file mode 100644
index 0000000..df3f826
--- /dev/null
+++ b/tc/server/default_coordinator.go
@@ -0,0 +1,659 @@
+package server
+
+import (
+ "fmt"
+ "github.com/dubbogo/getty"
+ "github.com/pkg/errors"
+ "go.uber.org/atomic"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "github.com/dk-lockdown/seata-golang/protocal/codec"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/tc/event"
+ "github.com/dk-lockdown/seata-golang/tc/holder"
+ "github.com/dk-lockdown/seata-golang/tc/lock"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+ "sync"
+ "time"
+)
+
+const (
+ RPC_REQUEST_TIMEOUT = 30 * time.Second
+ ALWAYS_RETRY_BOUNDARY = 0
+)
+
+// MessageFuture ...
+type MessageFuture struct {
+ id int32
+ err error
+ response interface{}
+ done chan bool
+}
+
+// NewMessageFuture ...
+func NewMessageFuture(message protocal.RpcMessage) *MessageFuture {
+ return &MessageFuture{
+ id: message.Id,
+ done: make(chan bool),
+ }
+}
+
+type DefaultCoordinator struct {
+ conf config.ServerConfig
+ core ITransactionCoordinator
+ idGenerator atomic.Uint32
+ futures *sync.Map
+ timeoutCheckTicker *time.Ticker
+ retryRollbackingTicker *time.Ticker
+ retryCommittingTicker *time.Ticker
+ asyncCommittingTicker *time.Ticker
+ undoLogDeleteTicker *time.Ticker
+}
+
+func NewDefaultCoordinator(conf config.ServerConfig) *DefaultCoordinator {
+ coordinator := &DefaultCoordinator{
+ conf: conf,
+ idGenerator: atomic.Uint32{},
+ futures: &sync.Map{},
+ timeoutCheckTicker: time.NewTicker(conf.TimeoutRetryPeriod),
+ retryRollbackingTicker: time.NewTicker(conf.RollbackingRetryPeriod),
+ retryCommittingTicker: time.NewTicker(conf.CommittingRetryPeriod),
+ asyncCommittingTicker: time.NewTicker(conf.AsynCommittingRetryPeriod),
+ undoLogDeleteTicker: time.NewTicker(conf.LogDeletePeriod),
+ }
+ core := NewCore(coordinator)
+ coordinator.core = core
+
+ go coordinator.processTimeoutCheck()
+ go coordinator.processRetryRollbacking()
+ go coordinator.processRetryCommitting()
+ go coordinator.processAsyncCommitting()
+ go coordinator.processUndoLogDelete()
+ return coordinator
+}
+
+
+func (coordinator *DefaultCoordinator) OnOpen(session getty.Session) error {
+ logging.Logger.Infof("got session:%s", session.Stat())
+ return nil
+}
+
+func (coordinator *DefaultCoordinator) OnError(session getty.Session, err error) {
+ session.Close()
+ logging.Logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err)
+}
+
+func (coordinator *DefaultCoordinator) OnClose(session getty.Session) {
+ logging.Logger.Info("session{%s} is closing......", session.Stat())
+}
+
+func (coordinator *DefaultCoordinator) OnMessage(session getty.Session, pkg interface{}) {
+ logging.Logger.Info("received message:{%v}", pkg)
+ rpcMessage,ok := pkg.(protocal.RpcMessage)
+ if ok {
+ _,isRegTM := rpcMessage.Body.(protocal.RegisterTMRequest)
+ if isRegTM {
+ coordinator.OnRegTmMessage(rpcMessage,session)
+ return
+ }
+
+ heartBeat,isHeartBeat := rpcMessage.Body.(protocal.HeartBeatMessage)
+ if isHeartBeat && heartBeat == protocal.HeartBeatMessagePing {
+ coordinator.OnCheckMessage(rpcMessage,session)
+ return
+ }
+
+ if rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST ||
+ rpcMessage.MessageType == protocal.MSGTYPE_RESQUEST_ONEWAY {
+ logging.Logger.Debugf("msgId:%s, body:%v", rpcMessage.Id, rpcMessage.Body)
+ _,isRegRM := rpcMessage.Body.(protocal.RegisterRMRequest)
+ if isRegRM {
+ coordinator.OnRegRmMessage(rpcMessage,session)
+ } else {
+ if SessionManager.IsRegistered(session) {
+ coordinator.OnTrxMessage(rpcMessage,session)
+ } else {
+ session.Close()
+ logging.Logger.Infof("close a unhandled connection! [%v]", session)
+ }
+ }
+ } else {
+ resp,loaded := coordinator.futures.Load(rpcMessage.Id)
+ if loaded {
+ response := resp.(*MessageFuture)
+ response.response = rpcMessage.Body
+ response.done <- true
+ coordinator.futures.Delete(rpcMessage.Id)
+ }
+ }
+ }
+}
+
+func (coordinator *DefaultCoordinator) OnCron(session getty.Session) {
+
+}
+
+/////////////////////////////////////////////////////////////
+// ServerMessageListener
+/////////////////////////////////////////////////////////////
+func (coordinator *DefaultCoordinator) OnTrxMessage(rpcMessage protocal.RpcMessage, session getty.Session) {
+ rpcContext := SessionManager.GetContextFromIdentified(session)
+ logging.Logger.Debugf("server received:%v,clientIp:%s,vgroup:%s",rpcMessage.Body,session.RemoteAddr(),rpcContext.TransactionServiceGroup)
+
+ warpMessage, isWarpMessage := rpcMessage.Body.(protocal.MergedWarpMessage)
+ if isWarpMessage {
+ resultMessage := protocal.MergeResultMessage{Msgs:make([]protocal.MessageTypeAware,0)}
+ for _,msg := range warpMessage.Msgs {
+ resp := coordinator.handleTrxMessage(msg,*rpcContext)
+ resultMessage.Msgs = append(resultMessage.Msgs,resp)
+ }
+ coordinator.SendResponse(rpcMessage,rpcContext.session,resultMessage)
+ } else {
+ message := rpcMessage.Body.(protocal.MessageTypeAware)
+ resp := coordinator.handleTrxMessage(message,*rpcContext)
+ coordinator.SendResponse(rpcMessage,rpcContext.session,resp)
+ }
+}
+
+func (coordinator *DefaultCoordinator) handleTrxMessage(msg protocal.MessageTypeAware,ctx RpcContext) protocal.MessageTypeAware {
+ switch msg.GetTypeCode() {
+ case protocal.TypeGlobalBegin:
+ req := msg.(protocal.GlobalBeginRequest)
+ resp := coordinator.doGlobalBegin(req,ctx)
+ return resp
+ case protocal.TypeGlobalStatus:
+ req := msg.(protocal.GlobalStatusRequest)
+ resp := coordinator.doGlobalStatus(req,ctx)
+ return resp
+ case protocal.TypeGlobalReport:
+ req := msg.(protocal.GlobalReportRequest)
+ resp := coordinator.doGlobalReport(req,ctx)
+ return resp
+ case protocal.TypeGlobalCommit:
+ req := msg.(protocal.GlobalCommitRequest)
+ resp := coordinator.doGlobalCommit(req,ctx)
+ return resp
+ case protocal.TypeGlobalRollback:
+ req := msg.(protocal.GlobalRollbackRequest)
+ resp := coordinator.doGlobalRollback(req,ctx)
+ return resp
+ case protocal.TypeBranchRegister:
+ req := msg.(protocal.BranchRegisterRequest)
+ resp := coordinator.doBranchRegister(req,ctx)
+ return resp
+ case protocal.TypeBranchStatusReport:
+ req := msg.(protocal.BranchReportRequest)
+ resp := coordinator.doBranchReport(req,ctx)
+ return resp
+ default:
+ return nil
+ }
+}
+
+func (coordinator *DefaultCoordinator) OnRegRmMessage(rpcMessage protocal.RpcMessage, session getty.Session) {
+ message := rpcMessage.Body.(protocal.RegisterRMRequest)
+
+ //version things
+ SessionManager.RegisterRmGettySession(message,session)
+ logging.Logger.Debugf("checkAuth for client:%s,vgroup:%s,applicationId:%s",session.RemoteAddr(),message.TransactionServiceGroup,message.ApplicationId)
+
+ coordinator.SendResponse(rpcMessage,session,protocal.RegisterRMResponse{AbstractIdentifyResponse: protocal.AbstractIdentifyResponse{Identified: true}})
+}
+
+func (coordinator *DefaultCoordinator) OnRegTmMessage(rpcMessage protocal.RpcMessage, session getty.Session) {
+ message := rpcMessage.Body.(protocal.RegisterTMRequest)
+
+ //version things
+ SessionManager.RegisterTmGettySession(message,session)
+ logging.Logger.Debugf("checkAuth for client:%s,vgroup:%s,applicationId:%s",session.RemoteAddr(),message.TransactionServiceGroup,message.ApplicationId)
+
+ coordinator.SendResponse(rpcMessage,session,protocal.RegisterTMResponse{AbstractIdentifyResponse: protocal.AbstractIdentifyResponse{Identified: true}})
+}
+
+func (coordinator *DefaultCoordinator) OnCheckMessage(rpcMessage protocal.RpcMessage, session getty.Session) {
+ coordinator.SendResponse(rpcMessage,session,protocal.HeartBeatMessagePong)
+ logging.Logger.Debugf("received PING from %s", session.RemoteAddr())
+}
+
+/////////////////////////////////////////////////////////////
+// ServerMessageSender
+/////////////////////////////////////////////////////////////
+func (coordinator *DefaultCoordinator) SendResponse(request protocal.RpcMessage, session getty.Session, msg interface{}) {
+ var ss = session
+ _,ok := msg.(protocal.HeartBeatMessage)
+ if !ok {
+ ss = SessionManager.GetSameClientGettySession(session)
+ }
+ if ss != nil {
+ coordinator.defaultSendResponse(request,ss,msg)
+ }
+}
+
+
+func (coordinator *DefaultCoordinator) SendSyncRequest(resourceId string, clientId string, message interface{}) (interface{},error) {
+ return coordinator.SendSyncRequestWithTimeout(resourceId,clientId,message,RPC_REQUEST_TIMEOUT)
+}
+
+
+func (coordinator *DefaultCoordinator) SendSyncRequestWithTimeout(resourceId string, clientId string, message interface{}, timeout time.Duration) (interface{},error) {
+ session,err := SessionManager.GetGettySession(resourceId,clientId)
+ if err != nil {
+ return nil, errors.WithStack(err)
+ }
+ return coordinator.sendAsyncRequestWithResponse("",session,message,timeout)
+}
+
+
+func (coordinator *DefaultCoordinator) SendSyncRequestByGettySession(session getty.Session, message interface{}) (interface{},error) {
+ return coordinator.SendSyncRequestByGettySessionWithTimeout(session,message,RPC_REQUEST_TIMEOUT)
+}
+
+
+func (coordinator *DefaultCoordinator) SendSyncRequestByGettySessionWithTimeout(session getty.Session, message interface{}, timeout time.Duration) (interface{},error) {
+ if session == nil {
+ return nil,errors.New("rm client is not connected")
+ }
+ return coordinator.sendAsyncRequestWithResponse("",session,message,timeout)
+}
+
+
+func (coordinator *DefaultCoordinator) SendASyncRequest(session getty.Session, message interface{}) error {
+ return coordinator.sendAsyncRequestWithoutResponse(session,message)
+}
+
+func (coordinator *DefaultCoordinator) sendAsyncRequestWithResponse(address string,session getty.Session,msg interface{},timeout time.Duration) (interface{},error) {
+ if timeout <= time.Duration(0) {
+ return nil,errors.New("timeout should more than 0ms")
+ }
+ return coordinator.sendAsyncRequest(address,session,msg,timeout)
+}
+
+func (coordinator *DefaultCoordinator) sendAsyncRequestWithoutResponse(session getty.Session,msg interface{}) error {
+ _,err := coordinator.sendAsyncRequest("",session,msg,time.Duration(0))
+ return err
+}
+
+func (coordinator *DefaultCoordinator) sendAsyncRequest(address string,session getty.Session,msg interface{},timeout time.Duration) (interface{},error) {
+ var err error
+ if session == nil {
+ logging.Logger.Warn("sendAsyncRequestWithResponse nothing, caused by null channel.")
+ }
+ rpcMessage := protocal.RpcMessage{
+ Id: int32(coordinator.idGenerator.Inc()),
+ MessageType: protocal.MSGTYPE_RESQUEST_ONEWAY,
+ Codec: codec.SEATA,
+ Compressor: 0,
+ Body: msg,
+ }
+ resp := NewMessageFuture(rpcMessage)
+ coordinator.futures.Store(rpcMessage.Id, resp)
+ //config timeout
+ err = session.WritePkg(rpcMessage, 60000)
+ if err != nil {
+ coordinator.futures.Delete(rpcMessage.Id)
+ }
+
+ if timeout > time.Duration(0) {
+ select {
+ case <-getty.GetTimeWheel().After(timeout):
+ coordinator.futures.Delete(rpcMessage.Id)
+ return nil, errors.Errorf("wait response timeout,ip:%s,request:%v", address, rpcMessage)
+ case <-resp.done:
+ err = resp.err
+ }
+ return resp.response, err
+ }
+ return nil,err
+}
+
+func (coordinator *DefaultCoordinator) defaultSendResponse(request protocal.RpcMessage, session getty.Session, msg interface{}) {
+ resp := protocal.RpcMessage{
+ Id: request.Id,
+ Codec: request.Codec,
+ Compressor: request.Compressor,
+ Body: msg,
+ }
+ _,ok := msg.(protocal.HeartBeatMessage)
+ if ok {
+ resp.MessageType = protocal.MSGTYPE_HEARTBEAT_RESPONSE
+ } else {
+ resp.MessageType = protocal.MSGTYPE_RESPONSE
+ }
+ session.WritePkg(resp,time.Duration(0))
+}
+
+/////////////////////////////////////////////////////////////
+// TCInboundHandler
+/////////////////////////////////////////////////////////////
+func (coordinator *DefaultCoordinator) doGlobalBegin(request protocal.GlobalBeginRequest,ctx RpcContext) protocal.GlobalBeginResponse {
+ var resp = protocal.GlobalBeginResponse{}
+ xid,err := coordinator.core.Begin(ctx.ApplicationId,ctx.TransactionServiceGroup,request.TransactionName,request.Timeout)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Xid = xid
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doGlobalStatus(request protocal.GlobalStatusRequest,ctx RpcContext) protocal.GlobalStatusResponse {
+ var resp = protocal.GlobalStatusResponse{}
+ globalStatus,err := coordinator.core.GetStatus(request.Xid)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.GlobalStatus = globalStatus
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doGlobalReport(request protocal.GlobalReportRequest,ctx RpcContext) protocal.GlobalReportResponse {
+ var resp = protocal.GlobalReportResponse{}
+ globalStatus,err := coordinator.core.GlobalReport(request.Xid,request.GlobalStatus)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.GlobalStatus = globalStatus
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+
+func (coordinator *DefaultCoordinator) doGlobalCommit(request protocal.GlobalCommitRequest,ctx RpcContext) protocal.GlobalCommitResponse {
+ var resp = protocal.GlobalCommitResponse{}
+ globalStatus,err := coordinator.core.Commit(request.Xid)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.GlobalStatus = globalStatus
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doGlobalRollback(request protocal.GlobalRollbackRequest,ctx RpcContext) protocal.GlobalRollbackResponse {
+ var resp = protocal.GlobalRollbackResponse{}
+ globalStatus,err := coordinator.core.Rollback(request.Xid)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ globalSession := holder.GetSessionHolder().FindGlobalSessionWithBranchSessions(request.Xid,false)
+ if globalSession == nil {
+ resp.GlobalStatus = meta.GlobalStatusFinished
+ } else {
+ resp.GlobalStatus = globalSession.Status
+ }
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.GlobalStatus = globalStatus
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doBranchRegister(request protocal.BranchRegisterRequest,ctx RpcContext) protocal.BranchRegisterResponse {
+ var resp = protocal.BranchRegisterResponse{}
+ branchId,err := coordinator.core.BranchRegister(request.BranchType,request.ResourceId,ctx.ClientId,request.Xid,request.ApplicationData,request.LockKey)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.BranchId = branchId
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doBranchReport(request protocal.BranchReportRequest,ctx RpcContext) protocal.BranchReportResponse {
+ var resp = protocal.BranchReportResponse{}
+ err := coordinator.core.BranchReport(request.BranchType,request.Xid,request.BranchId,request.Status,request.ApplicationData)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) doLockCheck(request protocal.GlobalLockQueryRequest,ctx RpcContext) protocal.GlobalLockQueryResponse {
+ var resp = protocal.GlobalLockQueryResponse{}
+ result, err := coordinator.core.LockQuery(request.BranchType,request.ResourceId,request.Xid,request.LockKey)
+ if err != nil {
+ trxException, ok := err.(meta.TransactionException)
+ resp.ResultCode = protocal.ResultCodeFailed
+ if ok {
+ resp.TransactionExceptionCode = trxException.Code
+ resp.Msg = fmt.Sprintf("TransactionException[%s]",err.Error())
+ logging.Logger.Errorf("Catch TransactionException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Msg = fmt.Sprintf("RuntimeException[%s]",err.Error())
+ logging.Logger.Errorf("Catch RuntimeException while do RPC, request: %v", request)
+ return resp
+ }
+ resp.Lockable = result
+ resp.ResultCode = protocal.ResultCodeSuccess
+ return resp
+}
+
+func (coordinator *DefaultCoordinator) processTimeoutCheck() {
+ for {
+ <- coordinator.timeoutCheckTicker.C
+ coordinator.timeoutCheck()
+ }
+}
+
+func (coordinator *DefaultCoordinator) processRetryRollbacking() {
+ for {
+ <- coordinator.retryRollbackingTicker.C
+ coordinator.handleRetryRollbacking()
+ }
+}
+
+func (coordinator *DefaultCoordinator) processRetryCommitting() {
+ for {
+ <- coordinator.retryCommittingTicker.C
+ coordinator.handleRetryCommitting()
+ }
+}
+
+func (coordinator *DefaultCoordinator) processAsyncCommitting() {
+ for {
+ <- coordinator.asyncCommittingTicker.C
+ coordinator.handleAsyncCommitting()
+ }
+}
+
+func (coordinator *DefaultCoordinator) processUndoLogDelete() {
+ for {
+ <- coordinator.undoLogDeleteTicker.C
+ coordinator.undoLogDelete()
+ }
+}
+
+func (coordinator *DefaultCoordinator) timeoutCheck() {
+ allSessions := holder.GetSessionHolder().RootSessionManager.AllSessions()
+ if allSessions == nil && len(allSessions) <= 0 {
+ return
+ }
+ logging.Logger.Debugf("Transaction Timeout Check Begin: %d",len(allSessions))
+ for _,globalSession := range allSessions {
+ logging.Logger.Debugf("%s %s %d %d",globalSession.Xid,globalSession.Status.String(),globalSession.BeginTime,globalSession.Timeout)
+ shouldTimout := func (gs *session.GlobalSession) bool {
+ globalSession.Lock()
+ defer globalSession.Unlock()
+ if globalSession.Status != meta.GlobalStatusBegin || !globalSession.IsTimeout() {
+ return false
+ }
+
+ if globalSession.Active {
+ globalSession.Active = false
+ }
+ changeGlobalSessionStatus(globalSession, meta.GlobalStatusTimeoutRollbacking)
+ evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC, globalSession.TransactionName, globalSession.BeginTime, 0, globalSession.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+ return true
+ }(globalSession)
+ if !shouldTimout {
+ continue
+ }
+ logging.Logger.Infof("Global transaction[%s] is timeout and will be rolled back.",globalSession.Status)
+ holder.GetSessionHolder().RetryRollbackingSessionManager.AddGlobalSession(globalSession)
+ }
+ logging.Logger.Debug("Transaction Timeout Check End.")
+}
+
+func (coordinator *DefaultCoordinator) handleRetryRollbacking() {
+ rollbackingSessions := holder.GetSessionHolder().RetryRollbackingSessionManager.AllSessions()
+ if rollbackingSessions == nil && len(rollbackingSessions) <= 0 {
+ return
+ }
+ now := util.CurrentTimeMillis()
+ for _,rollbackingSession := range rollbackingSessions {
+ if rollbackingSession.Status == meta.GlobalStatusRollbacking && !rollbackingSession.IsRollbackingDead() {
+ continue
+ }
+ if isRetryTimeout(int64(now),coordinator.conf.MaxRollbackRetryTimeout,rollbackingSession.BeginTime){
+ if coordinator.conf.RollbackRetryTimeoutUnlockEnable {
+ lock.GetLockManager().ReleaseGlobalSessionLock(rollbackingSession)
+ }
+ holder.GetSessionHolder().RetryRollbackingSessionManager.RemoveGlobalSession(rollbackingSession)
+ logging.Logger.Errorf("GlobalSession rollback retry timeout and removed [%s]", rollbackingSession.Xid)
+ continue
+ }
+ _, err := coordinator.core.doGlobalRollback(rollbackingSession,true)
+ if err != nil {
+ logging.Logger.Infof("Failed to retry rollbacking [%s]",rollbackingSession.Xid)
+ }
+ }
+}
+
+func isRetryTimeout(now int64,timeout int64,beginTime int64) bool {
+ if timeout >= ALWAYS_RETRY_BOUNDARY && now - beginTime > timeout {
+ return true
+ }
+ return false
+}
+
+func (coordinator *DefaultCoordinator) handleRetryCommitting() {
+ committingSessions := holder.GetSessionHolder().RetryCommittingSessionManager.AllSessions()
+ if committingSessions == nil && len(committingSessions) <= 0 {
+ return
+ }
+ now := util.CurrentTimeMillis()
+ for _,committingSession := range committingSessions {
+ if isRetryTimeout(int64(now),coordinator.conf.MaxCommitRetryTimeout,committingSession.BeginTime) {
+ holder.GetSessionHolder().RetryCommittingSessionManager.RemoveGlobalSession(committingSession)
+ logging.Logger.Errorf("GlobalSession commit retry timeout and removed [%s]", committingSession.Xid)
+ continue
+ }
+ _,err := coordinator.core.doGlobalCommit(committingSession,true)
+ if err != nil {
+ logging.Logger.Infof("Failed to retry committing [%s]",committingSession.Xid)
+ }
+ }
+}
+
+func (coordinator *DefaultCoordinator) handleAsyncCommitting() {
+ asyncCommittingSessions := holder.GetSessionHolder().AsyncCommittingSessionManager.AllSessions()
+ if asyncCommittingSessions == nil && len(asyncCommittingSessions) <= 0 {
+ return
+ }
+ for _,asyncCommittingSession := range asyncCommittingSessions {
+ if asyncCommittingSession.Status != meta.GlobalStatusAsyncCommitting {
+ continue
+ }
+ _,err := coordinator.core.doGlobalCommit(asyncCommittingSession,true)
+ if err != nil {
+ logging.Logger.Infof("Failed to async committing [%s]",asyncCommittingSession.Xid)
+ }
+ }
+}
+
+func (coordinator *DefaultCoordinator) undoLogDelete() {
+ saveDays := coordinator.conf.UndoConfig.LogSaveDays
+ for key,session := range SessionManager.GetRmSessions() {
+ resourceId := key
+ deleteRequest := protocal.UndoLogDeleteRequest{
+ ResourceId: resourceId,
+ SaveDays: saveDays,
+ }
+ err := coordinator.SendASyncRequest(session,deleteRequest)
+ if err != nil {
+ logging.Logger.Errorf("Failed to async delete undo log resourceId = %s", resourceId)
+ }
+ }
+}
+
+func (coordinator *DefaultCoordinator) Stop() {
+ coordinator.timeoutCheckTicker.Stop()
+ coordinator.retryRollbackingTicker.Stop()
+ coordinator.retryCommittingTicker.Stop()
+ coordinator.asyncCommittingTicker.Stop()
+ coordinator.undoLogDeleteTicker.Stop()
+}
\ No newline at end of file
diff --git a/tc/server/default_core.go b/tc/server/default_core.go
new file mode 100644
index 0000000..0feb584
--- /dev/null
+++ b/tc/server/default_core.go
@@ -0,0 +1,584 @@
+package server
+
+import (
+ "fmt"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "github.com/dk-lockdown/seata-golang/tc/event"
+ "github.com/dk-lockdown/seata-golang/tc/holder"
+ "github.com/dk-lockdown/seata-golang/tc/lock"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/util"
+)
+
+/**
+ * +--------------------+-----------------------+--------------------+
+ * | |Method(InBound) |Method(OutBound) |
+ * +--------------------+-----------------------+--------------------+
+ * | |Begin | |
+ * | |BranchRegister | |
+ * | TC |BranchReport | |
+ * | (AT&TCC) |(GlobalReport) |branchCommit |
+ * | (DefaultCore) |Commit |branchRollback |
+ * | |Rollback | |
+ * | |GetStatus | |
+ * +--------------------+-----------------------+--------------------+
+ * | AT |LockQuery | |
+ * +--------------------+-----------------------+--------------------+
+ * | |doGlobalCommit | |
+ * | SAGA |doGlobalRollBack | |
+ * | |doGlobalReport | |
+ * +--------------------+-----------------------+--------------------+
+ *
+ * 参考 [effective go 之 Embedding](#https://my.oschina.net/pengfeix/blog/109967)
+ * Go does not provide the typical, type-driven notion of subclassing,
+ * but it does have the ability to “borrow” pieces of an implementation
+ * by embedding types within a struct or interface.
+ * Go 没有像其它面向对象语言中的类继承概念,但是,它可以通过在结构体或者接口中嵌入
+ * 其它的类型,来使用被嵌入类型的功能。
+ *
+ * 原本 JAVA 版 Seata Sever 设计了 Core 接口,AbstractCore 实现该接口,ATCore、
+ * TccCore、SagaCore 都继承 AbstractCore。使 ATCore、TccCore、SagaCore 每一
+ * 个类单独拿出来都是 Core 接口的实现。但 Go 版的 Seata 我不打算这样设计。我们将
+ * Core 接口里定义的接口方法拿出来,如上面的表格所示,一个全局事务的周期分别对应 Begin、
+ * BranchRegister、BranchReport、Commit、Rollback 接口方法,这些接口方法适用于
+ * AT 模式和 TCC 模式(SAGA 模式暂不了解,先不考虑)。AT 模式会多一个 LockQuery
+ * 的接口。另外 OutBound 方向上有两个接口 branchCommit、branchRollback。JAVA 版
+ * 的设计中 doGlobalCommit、doGlobalRollBack、doGlobalReport 其实是私有方法,
+ * 这里用首字母小些开头的方法区分。那么 Go 版本的 DefaultCore 设计就出来了(暂不考虑 SAGA),
+ * DefaultCore 内嵌入 ATCore。
+ *
+ */
+
+type AbstractCore struct {
+ MessageSender IServerMessageSender
+}
+
+type ATCore struct {
+ AbstractCore
+}
+
+type SAGACore struct {
+ AbstractCore
+}
+
+type DefaultCore struct {
+ AbstractCore
+ ATCore
+ SAGACore
+ coreMap map[meta.BranchType]interface{}
+}
+
+func NewCore(sender IServerMessageSender) ITransactionCoordinator {
+ return &DefaultCore{
+ AbstractCore: AbstractCore{ MessageSender: sender },
+ ATCore: ATCore{},
+ SAGACore: SAGACore{},
+ coreMap: make(map[meta.BranchType]interface{}),
+ }
+}
+
+func (core *ATCore) branchSessionLock(globalSession *session.GlobalSession,branchSession *session.BranchSession) error {
+ result,err :=lock.GetLockManager().AcquireLock(branchSession)
+ if err != nil {
+ return err
+ }
+ if !result {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeLockKeyConflict,
+ Message: fmt.Sprintf("Global lock acquire failed xid = %s branchId = %s",
+ globalSession.Xid,branchSession.BranchId),
+ }
+ }
+ return nil
+}
+
+func (core *ATCore) branchSessionUnlock(branchSession *session.BranchSession) error {
+ _, err := lock.GetLockManager().ReleaseLock(branchSession)
+ return err
+}
+
+func (core *ATCore) LockQuery(branchType meta.BranchType,
+ resourceId string,
+ xid string,
+ lockKeys string) bool {
+ return lock.GetLockManager().IsLockable(xid,resourceId,lockKeys)
+}
+
+func (core *SAGACore) doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error) {
+ return true,nil
+}
+
+func (core *SAGACore) doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error) {
+ return true,nil
+}
+
+func (core *SAGACore) doGlobalReport(globalSession *session.GlobalSession, xid string, param meta.GlobalStatus) error {
+ return nil
+}
+
+func (core *DefaultCore) Begin(applicationId string, transactionServiceGroup string, name string, timeout int32) (string, error) {
+ gs := session.NewGlobalSession().
+ SetApplicationId(applicationId).
+ SetTransactionServiceGroup(transactionServiceGroup).
+ SetTransactionName(name).
+ SetTimeout(timeout)
+
+ gs.Begin()
+ err := holder.GetSessionHolder().RootSessionManager.AddGlobalSession(gs)
+ if err != nil {
+ return "",err
+ }
+
+ evt := event.NewGlobalTransactionEvent(gs.TransactionId, event.RoleTC,gs.TransactionName,gs.BeginTime,0,gs.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+
+ logging.Logger.Infof("Successfully begin global transaction xid = {}",gs.Xid)
+ return gs.Xid, nil
+}
+
+
+func (core *DefaultCore) BranchRegister(branchType meta.BranchType,
+ resourceId string,
+ clientId string,
+ xid string,
+ applicationData []byte,
+ lockKeys string) (int64, error) {
+ gs,err := assertGlobalSessionNotNull(xid,false)
+ if err != nil {
+ return 0,err
+ }
+ defer gs.Unlock()
+ gs.Lock()
+
+ err1 := globalSessionStatusCheck(gs)
+ if err1 != nil {
+ return 0,err
+ }
+
+ bs := session.NewBranchSessionByGlobal(*gs,branchType,resourceId,applicationData,lockKeys,clientId)
+
+ if branchType == meta.BranchTypeAT {
+ core.ATCore.branchSessionLock(gs, bs)
+ }
+ gs.Add(bs)
+
+ logging.Logger.Infof("Successfully register branch xid = %s, branchId = %d",gs.Xid,bs.BranchId)
+ return bs.BranchId,nil
+}
+
+func globalSessionStatusCheck(globalSession *session.GlobalSession) error {
+ if !globalSession.Active {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeGlobalTransactionNotActive,
+ Message: fmt.Sprintf("Could not register branch into global session xid = %s status = %d",globalSession.Xid,globalSession.Status),
+ }
+ }
+ if globalSession.Status != meta.GlobalStatusBegin {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeGlobalTransactionStatusInvalid,
+ Message: fmt.Sprintf("Could not register branch into global session xid = %s status = %d while expecting %d",
+ globalSession.Xid,globalSession.Status,meta.GlobalStatusBegin),
+ }
+ }
+ return nil
+}
+
+func assertGlobalSessionNotNull(xid string, withBranchSessions bool) (*session.GlobalSession,error) {
+ gs := holder.GetSessionHolder().FindGlobalSessionWithBranchSessions(xid,withBranchSessions)
+ if gs == nil {
+ logging.Logger.Errorf("Could not found global transaction xid = %s",gs.Xid)
+ return nil,&meta.TransactionException{
+ Code: meta.TransactionExceptionCodeGlobalTransactionNotExist,
+ Message: fmt.Sprintf("Could not found global transaction xid = %s",gs.Xid),
+ }
+ }
+ return gs,nil
+}
+
+func (core *DefaultCore) BranchReport(branchType meta.BranchType,
+ xid string,
+ branchId int64,
+ status meta.BranchStatus,
+ applicationData []byte) error {
+ gs,err := assertGlobalSessionNotNull(xid,true)
+ if err != nil {
+ return nil
+ }
+
+ bs := gs.GetBranch(branchId)
+ if bs == nil {
+ return &meta.TransactionException{
+ Code: meta.TransactionExceptionCodeBranchTransactionNotExist,
+ Message: fmt.Sprintf("Could not found branch session xid = %s branchId = %d",
+ xid,branchId),
+ }
+ }
+
+ bs.Status = status
+ holder.GetSessionHolder().RootSessionManager.UpdateBranchSessionStatus(bs,status)
+
+ logging.Logger.Infof("Successfully branch report xid = %s, branchId = %d",xid,bs.BranchId)
+ return nil
+}
+
+func (core *DefaultCore) LockQuery(branchType meta.BranchType, resourceId string, xid string, lockKeys string) (bool, error) {
+ return true,nil
+}
+
+func (core *DefaultCore) branchCommit(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) {
+ request := protocal.BranchCommitRequest{}
+ request.Xid = branchSession.Xid
+ request.BranchId = branchSession.BranchId
+ request.ResourceId = branchSession.ResourceId
+ request.ApplicationData = branchSession.ApplicationData
+ request.BranchType = branchSession.BranchType
+
+ resp, err := core.branchCommitSend(request,globalSession,branchSession)
+ if err != nil {
+ return 0,&meta.TransactionException{
+ Code: meta.TransactionExceptionCodeBranchTransactionNotExist,
+ Message: fmt.Sprintf("Send branch commit failed, xid = %s branchId = %d",
+ branchSession.Xid,branchSession.BranchId),
+ }
+ }
+ return resp,err
+}
+
+func (core *DefaultCore) branchCommitSend(request protocal.BranchCommitRequest,
+ globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus,error) {
+ resp,err := core.MessageSender.SendSyncRequest(branchSession.ResourceId,branchSession.ClientId,request)
+ if err != nil {
+ return 0,err
+ }
+ response := resp.(protocal.BranchCommitResponse)
+ return response.BranchStatus, nil
+}
+
+func (core *DefaultCore) branchRollback(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error) {
+ request := protocal.BranchRollbackRequest{}
+ request.Xid = branchSession.Xid
+ request.BranchId = branchSession.BranchId
+ request.ResourceId = branchSession.ResourceId
+ request.ApplicationData = branchSession.ApplicationData
+ request.BranchType = branchSession.BranchType
+
+ resp, err := core.branchRollbackSend(request,globalSession,branchSession)
+ if err != nil {
+ return 0,&meta.TransactionException{
+ Code: meta.TransactionExceptionCodeBranchTransactionNotExist,
+ Message: fmt.Sprintf("Send branch rollback failed, xid = %s branchId = %d",
+ branchSession.Xid,branchSession.BranchId),
+ }
+ }
+ return resp,err
+}
+
+func (core *DefaultCore) branchRollbackSend(request protocal.BranchRollbackRequest,
+ globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus,error) {
+ resp,err := core.MessageSender.SendSyncRequest(branchSession.ResourceId,branchSession.ClientId,request)
+ if err != nil {
+ return 0,err
+ }
+ response := resp.(protocal.BranchRollbackResponse)
+ return response.BranchStatus, nil
+}
+
+func (core *DefaultCore) Commit(xid string) (meta.GlobalStatus, error) {
+ globalSession := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid)
+ if globalSession == nil {
+ return meta.GlobalStatusFinished, nil
+ }
+ shouldCommit := func (gs *session.GlobalSession) bool {
+ gs.Lock()
+ defer gs.Unlock()
+ if gs.Active {
+ gs.Active = false
+ }
+ lock.GetLockManager().ReleaseGlobalSessionLock(gs)
+ if gs.Status == meta.GlobalStatusBegin {
+ changeGlobalSessionStatus(gs,meta.GlobalStatusCommitting)
+ return true
+ }
+ return false
+ }(globalSession)
+
+ if !shouldCommit {
+ return globalSession.Status,nil
+ }
+
+ if globalSession.CanBeCommittedAsync() {
+ asyncCommit(globalSession)
+ return meta.GlobalStatusCommitted, nil
+ } else {
+ _,err := core.doGlobalCommit(globalSession,false)
+ if err != nil {
+ return 0,err
+ }
+ }
+
+ return globalSession.Status,nil
+}
+
+func (core *DefaultCore) doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error) {
+ var (
+ success = true
+ err error
+ )
+
+ evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime,0,globalSession.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+
+ if globalSession.IsSaga() {
+ success,err = core.SAGACore.doGlobalCommit(globalSession,retrying)
+ } else {
+ for _,bs := range globalSession.GetSortedBranches() {
+ if bs.Status == meta.BranchStatusPhaseoneFailed {
+ removeBranchSession(globalSession,bs)
+ continue
+ }
+ branchStatus,err1 := core.branchCommit(globalSession,bs)
+ if err1 != nil {
+ logging.Logger.Errorf("Exception committing branch %v", bs)
+ if !retrying {
+ queueToRetryCommit(globalSession)
+ }
+ return false,err1
+ }
+ switch branchStatus {
+ case meta.BranchStatusPhasetwoCommitted:
+ removeBranchSession(globalSession,bs)
+ continue
+ case meta.BranchStatusPhasetwoCommitFailedUnretryable:
+ {
+ // 二阶段提交失败且不能 Retry,不能异步提交,则移除 GlobalSession,Why?
+ if globalSession.CanBeCommittedAsync() {
+ logging.Logger.Errorf("By [%s], failed to commit branch %v",bs.Status.String(),bs)
+ continue
+ } else {
+ endCommitFailed(globalSession)
+ logging.Logger.Errorf("Finally, failed to commit global[%d] since branch[%d] commit failed",globalSession.Xid,bs.BranchId)
+ return false,nil
+ }
+ }
+ default:
+ {
+ if !retrying {
+ queueToRetryCommit(globalSession)
+ return false,nil
+ }
+ if globalSession.CanBeCommittedAsync() {
+ logging.Logger.Errorf("By [%s], failed to commit branch %v",bs.Status.String(),bs)
+ continue
+ } else {
+ logging.Logger.Errorf("ResultCodeFailed to commit global[%d] since branch[%d] commit failed, will retry later.",globalSession.Xid,bs.BranchId)
+ return false,nil
+ }
+ }
+ }
+ }
+ if globalSession.HasBranch() {
+ logging.Logger.Infof("Global[%d] committing is NOT done.", globalSession.Xid)
+ return false,nil
+ }
+ }
+ if success {
+ endCommitted(globalSession)
+
+ evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime,
+ int64(util.CurrentTimeMillis()),globalSession.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+
+ logging.Logger.Infof("Global[%d] committing is successfully done.", globalSession.Xid)
+ }
+ return success,err
+}
+
+func (core *DefaultCore) Rollback(xid string) (meta.GlobalStatus, error) {
+ globalSession := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid)
+ if globalSession == nil {
+ return meta.GlobalStatusFinished, nil
+ }
+ shouldRollBack := func (gs *session.GlobalSession) bool {
+ gs.Lock()
+ defer gs.Unlock()
+ if gs.Active {
+ gs.Active = false // Highlight: Firstly, close the session, then no more branch can be registered.
+ }
+ lock.GetLockManager().ReleaseGlobalSessionLock(gs)
+ if gs.Status == meta.GlobalStatusBegin {
+ changeGlobalSessionStatus(gs,meta.GlobalStatusRollbacking)
+ return true
+ }
+ return false
+ }(globalSession)
+
+ if !shouldRollBack {
+ return globalSession.Status,nil
+ }
+
+ core.doGlobalRollback(globalSession,false)
+ return globalSession.Status,nil
+}
+
+func (core *DefaultCore) doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error) {
+ var (
+ success = true
+ err error
+ )
+
+ evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime, 0,globalSession.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+
+ if globalSession.IsSaga() {
+ success,err = core.SAGACore.doGlobalRollback(globalSession,retrying)
+ } else {
+ for _,bs := range globalSession.GetSortedBranches() {
+ if bs.Status == meta.BranchStatusPhaseoneFailed {
+ removeBranchSession(globalSession, bs)
+ continue
+ }
+ branchStatus,err1 := core.branchRollback(globalSession,bs)
+ if err1 != nil {
+ logging.Logger.Errorf("Exception rollbacking branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId)
+ if !retrying {
+ queueToRetryRollback(globalSession)
+ }
+ return false,err1
+ }
+ switch branchStatus {
+ case meta.BranchStatusPhasetwoRollbacked:
+ removeBranchSession(globalSession,bs)
+ logging.Logger.Infof("Successfully rollback branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId)
+ continue
+ case meta.BranchStatusPhasetwoRollbackFailedUnretryable:
+ endRollBackFailed(globalSession)
+ logging.Logger.Infof("ResultCodeFailed to rollback branch and stop retry xid=%d branchId=%d",globalSession.Xid,bs.BranchId)
+ return false,nil
+ default:
+ logging.Logger.Infof("ResultCodeFailed to rollback branch xid=%d branchId=%d", globalSession.Xid,bs.BranchId)
+ if !retrying {
+ queueToRetryRollback(globalSession)
+ }
+ return false,nil
+ }
+ }
+
+ // In db mode, there is a problem of inconsistent data in multiple copies, resulting in new branch
+ // transaction registration when rolling back.
+ // 1. New branch transaction and rollback branch transaction have no data association
+ // 2. New branch transaction has data association with rollback branch transaction
+ // The second query can solve the first problem, and if it is the second problem, it may cause a rollback
+ // failure due to data changes.
+ gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(globalSession.Xid)
+ if gs != nil && gs.HasBranch() {
+ logging.Logger.Infof("Global[%d] rollbacking is NOT done.", globalSession.Xid)
+ return false, nil
+ }
+ }
+ if success {
+ endRollbacked(globalSession)
+
+ evt := event.NewGlobalTransactionEvent(globalSession.TransactionId, event.RoleTC,globalSession.TransactionName,globalSession.BeginTime,
+ int64(util.CurrentTimeMillis()),globalSession.Status)
+ event.EventBus.GlobalTransactionEventChannel <- evt
+
+ logging.Logger.Infof("Successfully rollback global, xid = %d", globalSession.Xid)
+ }
+ return success,err
+}
+
+func (core *DefaultCore) GetStatus(xid string) (meta.GlobalStatus, error) {
+ gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid)
+ if gs == nil {
+ return meta.GlobalStatusFinished,nil
+ } else {
+ return gs.Status,nil
+ }
+}
+
+func (core *DefaultCore) GlobalReport(xid string, globalStatus meta.GlobalStatus) (meta.GlobalStatus, error) {
+ gs := holder.GetSessionHolder().RootSessionManager.FindGlobalSession(xid)
+ if gs == nil {
+ return globalStatus,nil
+ }
+ core.doGlobalReport(gs,xid,globalStatus)
+ return gs.Status,nil
+}
+
+func (core *DefaultCore) doGlobalReport(globalSession *session.GlobalSession,xid string,globalStatus meta.GlobalStatus) error {
+ if globalSession.IsSaga() {
+ return core.SAGACore.doGlobalReport(globalSession,xid,globalStatus)
+ }
+ return nil
+}
+
+func endRollbacked(globalSession *session.GlobalSession) {
+ if isTimeoutGlobalStatus(globalSession.Status) {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbacked)
+ } else {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbacked)
+ }
+ lock.GetLockManager().ReleaseGlobalSessionLock(globalSession)
+ holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession)
+}
+
+func endRollBackFailed(globalSession *session.GlobalSession) {
+ if isTimeoutGlobalStatus(globalSession.Status) {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbackFailed)
+ } else {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbackFailed)
+ }
+ lock.GetLockManager().ReleaseGlobalSessionLock(globalSession)
+ holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession)
+}
+
+func queueToRetryRollback(globalSession *session.GlobalSession) {
+ holder.GetSessionHolder().RetryRollbackingSessionManager.AddGlobalSession(globalSession)
+ if isTimeoutGlobalStatus(globalSession.Status) {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusTimeoutRollbackRetrying)
+ } else {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusRollbackRetrying)
+ }
+}
+
+func isTimeoutGlobalStatus(status meta.GlobalStatus) bool {
+ return status == meta.GlobalStatusTimeoutRollbacked ||
+ status == meta.GlobalStatusTimeoutRollbackFailed ||
+ status == meta.GlobalStatusTimeoutRollbacking ||
+ status == meta.GlobalStatusTimeoutRollbackRetrying
+}
+
+func endCommitted(globalSession *session.GlobalSession) {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitted)
+ lock.GetLockManager().ReleaseGlobalSessionLock(globalSession)
+ holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession)
+}
+
+func queueToRetryCommit(globalSession *session.GlobalSession) {
+ holder.GetSessionHolder().RetryCommittingSessionManager.AddGlobalSession(globalSession)
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitRetrying)
+}
+
+func endCommitFailed(globalSession *session.GlobalSession) {
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusCommitFailed)
+ lock.GetLockManager().ReleaseGlobalSessionLock(globalSession)
+ holder.GetSessionHolder().RootSessionManager.RemoveGlobalSession(globalSession)
+}
+
+func asyncCommit(globalSession *session.GlobalSession) {
+ holder.GetSessionHolder().AsyncCommittingSessionManager.AddGlobalSession(globalSession)
+ changeGlobalSessionStatus(globalSession,meta.GlobalStatusAsyncCommitting)
+}
+
+func changeGlobalSessionStatus(globalSession *session.GlobalSession, status meta.GlobalStatus) {
+ globalSession.Status = status
+ holder.GetSessionHolder().RootSessionManager.UpdateGlobalSessionStatus(globalSession,meta.GlobalStatusAsyncCommitting)
+}
+
+func removeBranchSession(globalSession *session.GlobalSession,branchSession *session.BranchSession) {
+ lock.GetLockManager().ReleaseLock(branchSession)
+ globalSession.Remove(branchSession)
+ holder.GetSessionHolder().RootSessionManager.RemoveBranchSession(globalSession,branchSession)
+}
\ No newline at end of file
diff --git a/tc/server/getty_session_manager.go b/tc/server/getty_session_manager.go
new file mode 100644
index 0000000..127add3
--- /dev/null
+++ b/tc/server/getty_session_manager.go
@@ -0,0 +1,372 @@
+package server
+
+import (
+ "github.com/pkg/errors"
+ "github.com/dubbogo/getty"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/model"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+var (
+ /**
+ * resourceId -> applicationId -> ip -> port -> RpcContext
+ */
+ rm_sessions = sync.Map{}
+
+ /**
+ * ip+appname -> port -> RpcContext
+ */
+ tm_sessions = sync.Map{}
+)
+
+const (
+ ClientIdSplitChar = ":"
+ DbkeysSplitChar = ","
+)
+
+type GettySessionManager struct {
+ IdentifiedSessions *sync.Map
+}
+
+var SessionManager GettySessionManager
+
+func init() {
+ SessionManager = GettySessionManager{IdentifiedSessions:&sync.Map{}}
+}
+
+func (manager *GettySessionManager) IsRegistered(session getty.Session) bool {
+ _,ok := manager.IdentifiedSessions.Load(session)
+ return ok
+}
+
+func (manager *GettySessionManager) GetRoleFromGettySession(session getty.Session) meta.TransactionRole {
+ context, ok := manager.IdentifiedSessions.Load(session)
+ if ok {
+ return context.(*RpcContext).ClientRole
+ }
+ return 0
+}
+
+func (manager *GettySessionManager) GetContextFromIdentified(session getty.Session) *RpcContext {
+ context, ok := manager.IdentifiedSessions.Load(session)
+ if ok {
+ rpcContext := context.(*RpcContext)
+ return rpcContext
+ }
+ return nil
+}
+
+func (manager *GettySessionManager) RegisterTmGettySession(request protocal.RegisterTMRequest,session getty.Session) {
+ //todo check version
+ rpcContext := buildGettySessionHolder(meta.TMROLE,request.Version,request.ApplicationId,request.TransactionServiceGroup,"",session)
+ rpcContext.HoldInIdentifiedGettySessions(manager.IdentifiedSessions)
+ clientIdentified := rpcContext.ApplicationId + ClientIdSplitChar + getClientIpFromGettySession(session)
+ clientIdentifiedMap,_ := tm_sessions.LoadOrStore(clientIdentified,&sync.Map{})
+ cMap := clientIdentifiedMap.(*sync.Map)
+ rpcContext.HoldInClientGettySessions(cMap)
+}
+
+func (manager *GettySessionManager) RegisterRmGettySession(resourceManagerRequest protocal.RegisterRMRequest,session getty.Session){
+ //todo check version
+ var rpcContext *RpcContext
+ dbKeySet := dbKeyToSet(resourceManagerRequest.ResourceIds)
+ context,ok := manager.IdentifiedSessions.Load(session)
+ if ok {
+ rpcContext = context.(*RpcContext)
+ rpcContext.AddResources(dbKeySet)
+ } else {
+ rpcContext = buildGettySessionHolder(meta.RMROLE,resourceManagerRequest.Version,resourceManagerRequest.ApplicationId,
+ resourceManagerRequest.TransactionServiceGroup,resourceManagerRequest.ResourceIds,session)
+ rpcContext.HoldInIdentifiedGettySessions(manager.IdentifiedSessions)
+ }
+ if dbKeySet == nil || dbKeySet.IsEmpty() { return }
+ for _,resourceId := range dbKeySet.List() {
+ applicationMap,_ := rm_sessions.LoadOrStore(resourceId,&sync.Map{})
+ aMap,_ := applicationMap.(*sync.Map)
+ ipMap,_ := aMap.LoadOrStore(resourceManagerRequest.ApplicationId,&sync.Map{})
+ iMap,_ := ipMap.(*sync.Map)
+ clientIp := getClientIpFromGettySession(session)
+ portMap,_ := iMap.LoadOrStore(clientIp,&sync.Map{})
+ pMap,_ := portMap.(*sync.Map)
+
+ rpcContext.HoldInResourceManagerGettySessions(resourceId,pMap)
+ // 老实讲,我不知道为什么要写这么一个方法,双重保证?
+ manager.updateGettySessionsResource(resourceId,clientIp,resourceManagerRequest.ApplicationId)
+ }
+}
+
+func (manager *GettySessionManager) updateGettySessionsResource(resourceId string,clientIp string,applicationId string) {
+ applicationMap,_ := rm_sessions.Load(resourceId)
+ aMap,_ := applicationMap.(*sync.Map)
+ ipMap,_ := aMap.Load(applicationId)
+ iMap,_ := ipMap.(*sync.Map)
+ portMap,_ := iMap.Load(clientIp)
+ pMap,_ := portMap.(*sync.Map)
+
+ rm_sessions.Range(func (key interface{},value interface{}) bool {
+ resourceKey,ok := key.(string)
+ if ok && resourceKey != resourceId {
+ appMap,_ := value.(*sync.Map)
+
+ clientIpMap,clientIpMapLoaded := appMap.Load(applicationId)
+ if clientIpMapLoaded {
+ cipMap,_ := clientIpMap.(*sync.Map)
+ clientPortMap, clientPortMapLoaded := cipMap.Load(clientIp)
+ if clientPortMapLoaded {
+ cpMap := clientPortMap.(*sync.Map)
+ cpMap.Range(func (key interface{},value interface{}) bool{
+ port,_ := key.(int)
+ rpcContext,_ := value.(*RpcContext)
+ _, ok := pMap.LoadOrStore(port,rpcContext)
+ if ok {
+ rpcContext.HoldInResourceManagerGettySessionsWithoutPortMap(resourceId,port)
+ }
+ return true
+ })
+ }
+ }
+ }
+ return true
+ })
+}
+
+func (manager *GettySessionManager) GetSameClientGettySession(session getty.Session) getty.Session {
+ if !session.IsClosed() {
+ return session
+ }
+
+ rpcContext := manager.GetContextFromIdentified(session)
+ if rpcContext == nil {
+ logging.Logger.Errorf("rpcContext is null,channel:{%v},active:{%t}",session,!session.IsClosed())
+ }
+ if !rpcContext.session.IsClosed() {
+ return rpcContext.session
+ }
+
+ clientPort := getClientPortFromGettySession(session)
+ if rpcContext.ClientRole == meta.TMROLE {
+ clientIdentified := rpcContext.ApplicationId + ClientIdSplitChar + getClientIpFromGettySession(session)
+ clientRpcMap, ok := tm_sessions.Load(clientIdentified)
+ if !ok {
+ return nil
+ }
+ clientMap := clientRpcMap.(*sync.Map)
+ return getGettySessionFromSameClientMap(clientMap,clientPort)
+ } else if rpcContext.ClientRole == meta.RMROLE {
+ var sameClientSession getty.Session
+ rpcContext.ClientRMHolderMap.Range(func (key interface{},value interface{}) bool {
+ clientRmMap := value.(*sync.Map)
+ sameClientSession = getGettySessionFromSameClientMap(clientRmMap,clientPort)
+ if sameClientSession != nil {
+ return false
+ }
+ return true
+ })
+ return sameClientSession
+ }
+ return nil
+}
+
+func getGettySessionFromSameClientMap(clientGettySessionMap *sync.Map,exclusivePort int) getty.Session {
+ var session getty.Session
+ if clientGettySessionMap != nil {
+ clientGettySessionMap.Range(func (key interface{},value interface{}) bool {
+ port,ok := key.(int)
+ if ok {
+ if port == exclusivePort {
+ clientGettySessionMap.Delete(key)
+ return true
+ }
+ }
+
+ context := value.(*RpcContext)
+ session = context.session
+ if !session.IsClosed() {
+ return false
+ }
+ clientGettySessionMap.Delete(key)
+ return true
+ })
+ }
+ return session
+}
+
+func (manager *GettySessionManager) GetGettySession(resourceId string,clientId string) (getty.Session,error) {
+ var resultSession getty.Session
+
+ clientIdInfo := strings.Split(clientId,ClientIdSplitChar)
+ if clientIdInfo == nil || len(clientIdInfo) != 3 {
+ return nil,errors.Errorf("Invalid Client ID:%d",clientId)
+ }
+ targetApplicationId := clientIdInfo[0]
+ targetIP := clientIdInfo[1]
+ targetPort,_ := strconv.Atoi(clientIdInfo[2])
+
+ applicationMap,ok := rm_sessions.Load(resourceId)
+ if targetApplicationId == "" || !ok || applicationMap == nil {
+ logging.Logger.Infof("No channel is available for resource[%s]",resourceId)
+ }
+ appMap,_ := applicationMap.(*sync.Map)
+
+ clientIpMap,clientIpMapLoaded := appMap.Load(targetApplicationId)
+ if clientIpMapLoaded {
+ ipMap,_ := clientIpMap.(*sync.Map)
+
+ portMap,portMapLoaded := ipMap.Load(targetIP)
+ if portMapLoaded {
+ pMap,_ := portMap.(*sync.Map)
+ context,contextLoaded := pMap.Load(targetPort)
+ // Firstly, try to find the original channel through which the branch was registered.
+ if contextLoaded {
+ rpcContext := context.(*RpcContext)
+ if !rpcContext.session.IsClosed() {
+ resultSession = rpcContext.session
+ logging.Logger.Debugf("Just got exactly the one %v for %s",rpcContext.session,clientId)
+ } else {
+ pMap.Delete(targetPort)
+ logging.Logger.Infof("Removed inactive %d",rpcContext.session)
+ }
+ }
+
+ // The original channel was broken, try another one.
+ if resultSession == nil {
+ pMap.Range(func (key interface{},value interface{}) bool {
+ rpcContext := value.(*RpcContext)
+
+ if !rpcContext.session.IsClosed() {
+ resultSession = rpcContext.session
+ logging.Logger.Infof("Choose %v on the same IP[%s] as alternative of %s",rpcContext.session,targetIP,clientId)
+ //跳出 range 循环
+ return false
+ } else {
+ pMap.Delete(key)
+ logging.Logger.Infof("Removed inactive %d",rpcContext.session)
+ }
+ return true
+ })
+ }
+ }
+
+ // No channel on the this app node, try another one.
+ if resultSession == nil {
+ ipMap.Range(func (key interface{},value interface{}) bool {
+ ip := key.(string)
+ if ip == targetIP { return true }
+
+ portMapOnOtherIP,_ := value.(*sync.Map)
+ if portMapOnOtherIP == nil { return true }
+
+ portMapOnOtherIP.Range(func (key interface{},value interface {}) bool {
+ rpcContext := value.(*RpcContext)
+
+ if !rpcContext.session.IsClosed() {
+ resultSession = rpcContext.session
+ logging.Logger.Infof("Choose %v on the same application[%s] as alternative of %s",rpcContext.session,targetApplicationId,clientId)
+ //跳出 range 循环
+ return false
+ } else {
+ portMapOnOtherIP.Delete(key)
+ logging.Logger.Infof("Removed inactive %d",rpcContext.session)
+ }
+ return true
+ })
+
+ if resultSession != nil { return false }
+ return true
+ })
+ }
+ }
+
+ if resultSession == nil {
+ resultSession = tryOtherApp(appMap,targetApplicationId)
+ if resultSession == nil {
+ logging.Logger.Infof("No channel is available for resource[%s] as alternative of %s",resourceId,clientId)
+ } else {
+ logging.Logger.Infof("Choose %v on the same resource[%s] as alternative of %s", resultSession, resourceId, clientId)
+ }
+ }
+ return resultSession,nil
+}
+
+func tryOtherApp(applicationMap *sync.Map,myApplicationId string) getty.Session {
+ var chosenChannel getty.Session
+ applicationMap.Range(func (key interface{},value interface{}) bool {
+ applicationId := key.(string)
+ if myApplicationId != "" && applicationId == myApplicationId {return true}
+
+ targetIPMap,_ := value.(*sync.Map)
+ targetIPMap.Range(func (key interface{},value interface{}) bool {
+ if value == nil { return true }
+ portMap,_ := value.(*sync.Map)
+
+ portMap.Range(func (key interface{},value interface{}) bool {
+ rpcContext := value.(*RpcContext)
+ if !rpcContext.session.IsClosed() {
+ chosenChannel = rpcContext.session
+ return false
+ } else {
+ portMap.Delete(key)
+ logging.Logger.Infof("Removed inactive %d",rpcContext.session)
+ }
+ return true
+ })
+ if chosenChannel != nil { return false }
+ return true
+ })
+ if chosenChannel != nil { return false }
+ return true
+ })
+ return chosenChannel
+}
+
+
+
+func buildGettySessionHolder(role meta.TransactionRole,version string,applicationId string,
+ txServiceGroup string,dbKeys string,session getty.Session) *RpcContext {
+ return &RpcContext{
+ ClientRole: role,
+ Version: version,
+ ApplicationId: applicationId,
+ TransactionServiceGroup: txServiceGroup,
+ ClientId: buildClientId(applicationId,session),
+ session: session,
+ ResourceSets: dbKeyToSet(dbKeys),
+ }
+}
+
+func dbKeyToSet(dbKey string) *model.Set {
+ if dbKey == "" {
+ return nil
+ }
+ keys := strings.Split(dbKey,DbkeysSplitChar)
+ set := model.NewSet()
+ for _,key := range keys {
+ set.Add(key)
+ }
+ return set
+}
+
+func buildClientId(applicationId string, session getty.Session) string {
+ return applicationId + ClientIdSplitChar + session.RemoteAddr()
+}
+
+
+func (manager *GettySessionManager) GetRmSessions() map[string]getty.Session {
+ sessions := make(map[string]getty.Session)
+ rm_sessions.Range(func (key interface{},value interface{}) bool {
+ resourceId,_ := key.(string)
+ applicationMap := value.(*sync.Map)
+ session := tryOtherApp(applicationMap,"")
+ if session == nil {
+ return false
+ }
+ sessions[resourceId] = session
+ return true
+ })
+ return sessions
+}
\ No newline at end of file
diff --git a/tc/server/readwriter.go b/tc/server/readwriter.go
new file mode 100644
index 0000000..e5acff5
--- /dev/null
+++ b/tc/server/readwriter.go
@@ -0,0 +1,190 @@
+package server
+
+import (
+ "bytes"
+ "github.com/dubbogo/getty"
+ "github.com/pkg/errors"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "github.com/dk-lockdown/seata-golang/protocal/codec"
+ "vimagination.zapto.org/byteio"
+)
+
+/**
+ * <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()
+}
\ No newline at end of file
diff --git a/tc/server/rpc_context.go b/tc/server/rpc_context.go
new file mode 100644
index 0000000..11ac2eb
--- /dev/null
+++ b/tc/server/rpc_context.go
@@ -0,0 +1,136 @@
+package server
+
+import (
+ "errors"
+ "github.com/dubbogo/getty"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/model"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+const IpPortSplitChar = ":"
+
+type RpcContext struct {
+ ClientRole meta.TransactionRole
+ Version string
+ ApplicationId string
+ TransactionServiceGroup string
+ ClientId string
+ session getty.Session
+ ResourceSets *model.Set
+
+ /**
+ * <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
+}
\ No newline at end of file
diff --git a/tc/server/server.go b/tc/server/server.go
new file mode 100644
index 0000000..1aecd88
--- /dev/null
+++ b/tc/server/server.go
@@ -0,0 +1,121 @@
+package server
+
+import (
+ "fmt"
+ "net"
+ "os"
+ "os/signal"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "syscall"
+ "time"
+)
+
+import (
+ "github.com/dubbogo/getty"
+ "github.com/dubbogo/gost/sync"
+)
+
+var (
+ srvGrpool *gxsync.TaskPool
+)
+
+
+func SetServerGrpool() {
+ srvConf := config.GetServerConfig()
+ if srvConf.GettyConfig.GrPoolSize > 1 {
+ srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GettyConfig.GrPoolSize),
+ gxsync.WithTaskPoolTaskQueueLength(srvConf.GettyConfig.QueueLen),
+ gxsync.WithTaskPoolTaskQueueNumber(srvConf.GettyConfig.QueueNumber))
+ }
+}
+
+type Server struct {
+ conf config.ServerConfig
+ tcpServer getty.Server
+ rpcHandler *DefaultCoordinator
+}
+
+func NewServer() *Server {
+
+ s := &Server{
+ conf: config.GetServerConfig(),
+ }
+ coordinator := NewDefaultCoordinator(s.conf)
+ s.rpcHandler = coordinator
+
+ return s
+}
+
+func (s *Server) newSession(session getty.Session) error {
+ var (
+ ok bool
+ tcpConn *net.TCPConn
+ )
+ conf := s.conf
+
+ if conf.GettyConfig.GettySessionParam.CompressEncoding {
+ session.SetCompressType(getty.CompressZip)
+ }
+
+ if tcpConn, ok = session.Conn().(*net.TCPConn); !ok {
+ panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn()))
+ }
+
+ tcpConn.SetNoDelay(conf.GettyConfig.GettySessionParam.TcpNoDelay)
+ tcpConn.SetKeepAlive(conf.GettyConfig.GettySessionParam.TcpKeepAlive)
+ if conf.GettyConfig.GettySessionParam.TcpKeepAlive {
+ tcpConn.SetKeepAlivePeriod(conf.GettyConfig.GettySessionParam.KeepAlivePeriod)
+ }
+ tcpConn.SetReadBuffer(conf.GettyConfig.GettySessionParam.TcpRBufSize)
+ tcpConn.SetWriteBuffer(conf.GettyConfig.GettySessionParam.TcpWBufSize)
+
+ session.SetName(conf.GettyConfig.GettySessionParam.SessionName)
+ session.SetMaxMsgLen(conf.GettyConfig.GettySessionParam.MaxMsgLen)
+ session.SetPkgHandler(RpcServerPkgHandler)
+ session.SetEventListener(s.rpcHandler)
+ session.SetWQLen(conf.GettyConfig.GettySessionParam.PkgWQSize)
+ session.SetReadTimeout(conf.GettyConfig.GettySessionParam.TcpReadTimeout)
+ session.SetWriteTimeout(conf.GettyConfig.GettySessionParam.TcpWriteTimeout)
+ session.SetCronPeriod((int)(conf.GettyConfig.SessionTimeout.Nanoseconds() / 1e6))
+ session.SetWaitTime(conf.GettyConfig.GettySessionParam.WaitTimeout)
+ logging.Logger.Debugf("app accepts new session:%s\n", session.Stat())
+
+ session.SetTaskPool(srvGrpool)
+
+ return nil
+}
+
+func (s *Server) Start(addr string) {
+ var (
+ tcpServer getty.Server
+ )
+
+ tcpServer = getty.NewTCPServer(
+ getty.WithLocalAddress(addr),
+ )
+ tcpServer.RunEventLoop(s.newSession)
+ logging.Logger.Debugf("s bind addr{%s} ok!", addr)
+ s.tcpServer = tcpServer
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
+ for {
+ sig := <-c
+ logging.Logger.Info("get a signal %s", sig.String())
+ switch sig {
+ case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
+ s.Stop()
+ time.Sleep(time.Second)
+ return
+ case syscall.SIGHUP:
+ default:
+ return
+ }
+ }
+}
+
+func (s *Server) Stop() {
+ s.tcpServer.Close()
+ s.rpcHandler.Stop()
+}
diff --git a/tc/server/server_message_listener.go b/tc/server/server_message_listener.go
new file mode 100644
index 0000000..56fff60
--- /dev/null
+++ b/tc/server/server_message_listener.go
@@ -0,0 +1,16 @@
+package server
+
+import (
+ "github.com/dubbogo/getty"
+ "github.com/dk-lockdown/seata-golang/protocal"
+)
+
+type IServerMessageListener interface {
+ OnTrxMessage(rpcMessage protocal.RpcMessage, session getty.Session)
+
+ OnRegRmMessage(request protocal.RpcMessage, session getty.Session)
+
+ OnRegTmMessage(request protocal.RpcMessage, session getty.Session)
+
+ OnCheckMessage(request protocal.RpcMessage, session getty.Session)
+}
\ No newline at end of file
diff --git a/tc/server/server_message_sender.go b/tc/server/server_message_sender.go
new file mode 100644
index 0000000..94f7f3d
--- /dev/null
+++ b/tc/server/server_message_sender.go
@@ -0,0 +1,78 @@
+package server
+
+import (
+ "github.com/dubbogo/getty"
+ "github.com/dk-lockdown/seata-golang/protocal"
+ "time"
+)
+
+type IServerMessageSender interface {
+ /**
+ * Send response.
+ *
+ * @param request the request
+ * @param channel the channel
+ * @param msg the msg
+ */
+
+ SendResponse(request protocal.RpcMessage, session getty.Session, msg interface{})
+
+ /**
+ * Sync call to RM
+ *
+ * @param resourceId Resource ID
+ * @param clientId Client ID
+ * @param message Request message
+ * @return Response message
+ * @throws IOException .
+ * @throws TimeoutException the timeout exception
+ */
+ SendSyncRequest(resourceId string, clientId string, message interface{}) (interface{},error)
+
+ /**
+ * Sync call to RM with timeout.
+ *
+ * @param resourceId Resource ID
+ * @param clientId Client ID
+ * @param message Request message
+ * @param timeout timeout of the call
+ * @return Response message
+ * @throws IOException .
+ * @throws TimeoutException the timeout exception
+ */
+ SendSyncRequestWithTimeout(resourceId string, clientId string, message interface{}, timeout time.Duration) (interface{},error)
+
+ /**
+ * Send request with response object.
+ * send syn request for rm
+ *
+ * @param clientChannel the client channel
+ * @param message the message
+ * @return the object
+ * @throws TimeoutException the timeout exception
+ */
+ SendSyncRequestByGettySession(session getty.Session, message interface{}) (interface{},error)
+
+ /**
+ * Send request with response object.
+ * send syn request for rm
+ *
+ * @param clientChannel the client channel
+ * @param message the message
+ * @param timeout the timeout
+ * @return the object
+ * @throws TimeoutException the timeout exception
+ */
+ SendSyncRequestByGettySessionWithTimeout(session getty.Session, message interface{}, timeout time.Duration) (interface{},error)
+
+ /**
+ * ASync call to RM
+ *
+ * @param channel channel
+ * @param message Request message
+ * @return Response message
+ * @throws IOException .
+ * @throws TimeoutException the timeout exception
+ */
+ SendASyncRequest(session getty.Session, message interface{}) error
+}
diff --git a/tc/server/tc_inbound_handler.go b/tc/server/tc_inbound_handler.go
new file mode 100644
index 0000000..e8395c8
--- /dev/null
+++ b/tc/server/tc_inbound_handler.go
@@ -0,0 +1,14 @@
+package server
+
+import "github.com/dk-lockdown/seata-golang/protocal"
+
+type TCInboundHandler interface {
+ doGlobalBegin(request protocal.GlobalBeginRequest,ctx RpcContext) protocal.GlobalBeginResponse
+ doGlobalStatus(request protocal.GlobalStatusRequest,ctx RpcContext) protocal.GlobalStatusResponse
+ doGlobalReport(request protocal.GlobalReportRequest,ctx RpcContext) protocal.GlobalReportResponse
+ doGlobalCommit(request protocal.GlobalCommitRequest,ctx RpcContext) protocal.GlobalCommitResponse
+ doGlobalRollback(request protocal.GlobalRollbackRequest,ctx RpcContext) protocal.GlobalRollbackResponse
+ doBranchRegister(request protocal.BranchRegisterRequest,ctx RpcContext) protocal.BranchRegisterResponse
+ doBranchReport(request protocal.BranchReportRequest,ctx RpcContext) protocal.BranchReportResponse
+ doLockCheck(request protocal.GlobalLockQueryRequest,ctx RpcContext) protocal.GlobalLockQueryResponse
+}
\ No newline at end of file
diff --git a/tc/server/transaction_coordinator.go b/tc/server/transaction_coordinator.go
new file mode 100644
index 0000000..ca1e7d1
--- /dev/null
+++ b/tc/server/transaction_coordinator.go
@@ -0,0 +1,73 @@
+package server
+
+import (
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/rm"
+ "github.com/dk-lockdown/seata-golang/tc/session"
+ "github.com/dk-lockdown/seata-golang/tm"
+)
+
+type ITransactionCoordinatorInbound interface {
+ tm.ITransactionManager
+ rm.IResourceManagerOutbound
+}
+
+type ITransactionCoordinatorOutbound interface {
+ /**
+ * Commit a branch transaction.
+ *
+ * @param globalSession the global session
+ * @param branchSession the branch session
+ * @return Status of the branch after committing.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ branchCommit(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error)
+
+ /**
+ * Rollback a branch transaction.
+ *
+ * @param globalSession the global session
+ * @param branchSession the branch session
+ * @return Status of the branch after rollbacking.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ branchRollback(globalSession *session.GlobalSession, branchSession *session.BranchSession) (meta.BranchStatus, error)
+
+}
+
+type ITransactionCoordinator interface {
+ ITransactionCoordinatorInbound
+ ITransactionCoordinatorOutbound
+
+ /**
+ * Do global commit.
+ *
+ * @param globalSession the global session
+ * @param retrying the retrying
+ * @return is global commit.
+ * @throws TransactionException the transaction exception
+ */
+ doGlobalCommit(globalSession *session.GlobalSession, retrying bool) (bool, error)
+
+ /**
+ * Do global rollback.
+ *
+ * @param globalSession the global session
+ * @param retrying the retrying
+ * @return is global rollback.
+ * @throws TransactionException the transaction exception
+ */
+ doGlobalRollback(globalSession *session.GlobalSession, retrying bool) (bool, error)
+
+ /**
+ * Do global report.
+ *
+ * @param globalSession the global session
+ * @param xid Transaction id.
+ * @param param the global status
+ * @throws TransactionException the transaction exception
+ */
+ doGlobalReport(globalSession *session.GlobalSession, xid string, param meta.GlobalStatus) error
+}
diff --git a/tc/session/branch_session.go b/tc/session/branch_session.go
new file mode 100644
index 0000000..881c562
--- /dev/null
+++ b/tc/session/branch_session.go
@@ -0,0 +1,230 @@
+package session
+
+import (
+ "bytes"
+ "github.com/pkg/errors"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/util"
+ "vimagination.zapto.org/byteio"
+)
+
+type BranchSession struct{
+ Xid string
+
+ TransactionId int64
+
+ BranchId int64
+
+ ResourceGroupId string
+
+ ResourceId string
+
+ LockKey string
+
+ BranchType meta.BranchType
+
+ Status meta.BranchStatus
+
+ ClientId string
+
+ ApplicationData []byte
+}
+
+func NewBranchSession() *BranchSession {
+ return &BranchSession{}
+}
+
+func NewBranchSessionByGlobal(gs GlobalSession,
+ branchType meta.BranchType,
+ resourceId string,
+ applicationData []byte,
+ lockKeys string,
+ clientId string) *BranchSession {
+ bs := NewBranchSession()
+ bs.SetXid(gs.Xid)
+ bs.SetTransactionId(gs.TransactionId)
+ bs.SetBranchId(util.GeneratorUUID())
+ bs.SetBranchType(branchType)
+ bs.SetResourceId(resourceId)
+ bs.SetLockKey(lockKeys)
+ bs.SetClientId(clientId)
+ bs.SetApplicationData(applicationData)
+ return bs
+}
+
+
+func (bs *BranchSession) SetXid(xid string) *BranchSession {
+ bs.Xid = xid
+ return bs
+}
+
+func (bs *BranchSession) SetTransactionId(transactionId int64) *BranchSession {
+ bs.TransactionId = transactionId
+ return bs
+}
+
+func (bs *BranchSession) SetBranchId(branchId int64) *BranchSession {
+ bs.BranchId = branchId
+ return bs
+}
+
+
+func (bs *BranchSession) SetResourceGroupId(ResourceGroupId string) *BranchSession {
+ bs.ResourceGroupId = ResourceGroupId
+ return bs
+}
+
+func (bs *BranchSession) SetResourceId(resourceId string) *BranchSession {
+ bs.ResourceId = resourceId
+ return bs
+}
+
+func (bs *BranchSession) SetLockKey(lockKey string) *BranchSession {
+ bs.LockKey = lockKey
+ return bs
+}
+
+func (bs *BranchSession) SetBranchType(branchType meta.BranchType) *BranchSession {
+ bs.BranchType = branchType
+ return bs
+}
+
+func (bs *BranchSession) SetStatus(status meta.BranchStatus) *BranchSession {
+ bs.Status = status
+ return bs
+}
+
+func (bs *BranchSession) SetClientId(clientId string) *BranchSession {
+ bs.ClientId = clientId
+ return bs
+}
+
+func (bs *BranchSession) SetApplicationData(applicationData []byte) *BranchSession {
+ bs.ApplicationData = applicationData
+ return bs
+}
+
+func (bs *BranchSession) CompareTo(session *BranchSession) int {
+ return int(bs.BranchId - session.BranchId)
+}
+
+func (bs *BranchSession) Encode() ([]byte, error) {
+ var (
+ zero32 int32 = 0
+ zero16 int16 = 0
+ )
+
+ size := calBranchSessionSize(len(bs.ResourceId),len(bs.LockKey),len(bs.ClientId),len(bs.ApplicationData),len(bs.Xid))
+
+ if size > config.GetStoreConfig().MaxBranchSessionSize {
+ if bs.LockKey == "" {
+ logging.Logger.Errorf("branch session size exceeded, size : %d maxBranchSessionSize : %d", size, config.GetStoreConfig().MaxBranchSessionSize)
+ //todo compress
+ return nil, errors.New("branch session size exceeded.")
+ }
+ }
+
+ var b bytes.Buffer
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ w.WriteInt64(bs.TransactionId)
+ w.WriteInt64(bs.BranchId)
+ if bs.ResourceId != "" {
+ w.WriteUint32(uint32(len(bs.ResourceId)))
+ w.WriteString( bs.ResourceId)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ if bs.LockKey != "" {
+ w.WriteUint32(uint32(len(bs.LockKey)))
+ w.WriteString( bs.LockKey)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ if bs.ClientId != "" {
+ w.WriteUint16(uint16(len(bs.ClientId)))
+ w.WriteString(bs.ClientId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if bs.ApplicationData != nil {
+ w.WriteUint32(uint32(len(bs.ApplicationData)))
+ w.Write(bs.ApplicationData)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ if bs.Xid != "" {
+ w.WriteUint32(uint32(len(bs.Xid)))
+ w.WriteString( bs.Xid)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ w.WriteByte(byte(bs.BranchType))
+ w.WriteByte(byte(bs.Status))
+
+ return b.Bytes(), nil
+}
+
+func (bs *BranchSession) Decode(b []byte) {
+ var length32 uint32 = 0
+ var length16 uint16 = 0
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(b)}
+
+ bs.TransactionId, _, _ = r.ReadInt64()
+ bs.BranchId, _, _ = r.ReadInt64()
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 { bs.ResourceId, _, _ = r.ReadString(int(length32)) }
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 { bs.LockKey, _, _ = r.ReadString(int(length32)) }
+
+ length16, _, _ = r.ReadUint16()
+ if length16 > 0 { bs.ClientId, _, _ = r.ReadString(int(length16)) }
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 {
+ bs.ApplicationData = make([]byte,int(length32))
+ r.Read(bs.ApplicationData)
+ }
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 { bs.Xid, _, _ = r.ReadString(int(length32)) }
+
+ branchType, _ := r.ReadByte()
+ bs.BranchType = meta.BranchType(branchType)
+
+ status, _ := r.ReadByte()
+ bs.Status = meta.BranchStatus(status)
+}
+
+func calBranchSessionSize(resourceIdLen int,
+ lockKeyLen int,
+ clientIdLen int,
+ applicationDataLen int,
+ xidLen int) int{
+
+ size := 8 + // transactionId
+ 8 + // branchId
+ 4 + // resourceIdBytes.length
+ 4 + // lockKeyBytes.length
+ 2 + // clientIdBytes.length
+ 4 + // applicationDataBytes.length
+ 4 + // xidBytes.size
+ 1 + // statusCode
+ resourceIdLen +
+ lockKeyLen +
+ clientIdLen +
+ applicationDataLen +
+ xidLen +
+ 1
+
+ return size
+}
diff --git a/tc/session/branch_session_test.go b/tc/session/branch_session_test.go
new file mode 100644
index 0000000..9df8d23
--- /dev/null
+++ b/tc/session/branch_session_test.go
@@ -0,0 +1,37 @@
+package session
+
+import (
+ "github.com/stretchr/testify/assert"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/util"
+ "testing"
+)
+
+func TestBranchSession_Encode_Decode(t *testing.T) {
+ bs := branchSessionProvider()
+ result,_ := bs.Encode()
+ newBs := &BranchSession{}
+ newBs.Decode(result)
+
+ assert.Equal(t,bs.TransactionId,newBs.TransactionId)
+ assert.Equal(t,bs.BranchId,newBs.BranchId)
+ assert.Equal(t,bs.ResourceId,newBs.ResourceId)
+ assert.Equal(t,bs.LockKey,newBs.LockKey)
+ assert.Equal(t,bs.ClientId,newBs.ClientId)
+ assert.Equal(t,bs.ApplicationData,newBs.ApplicationData)
+}
+
+func branchSessionProvider() *BranchSession {
+ bs := NewBranchSession().
+ SetTransactionId(util.GeneratorUUID()).
+ SetBranchId(1).
+ SetResourceGroupId("my_test_tx_group").
+ SetResourceId("tb_1").
+ SetLockKey("t_1").
+ SetBranchType(meta.BranchTypeAT).
+ SetStatus(meta.BranchStatusUnknown).
+ SetClientId("c1").
+ SetApplicationData([]byte("{\"data\":\"test\"}"))
+
+ return bs
+}
\ No newline at end of file
diff --git a/tc/session/global_session.go b/tc/session/global_session.go
new file mode 100644
index 0000000..8109470
--- /dev/null
+++ b/tc/session/global_session.go
@@ -0,0 +1,297 @@
+package session
+
+import (
+ "bytes"
+ "github.com/pkg/errors"
+ "github.com/dk-lockdown/seata-golang/common"
+ "github.com/dk-lockdown/seata-golang/logging"
+ "github.com/dk-lockdown/seata-golang/meta"
+ "github.com/dk-lockdown/seata-golang/tc/config"
+ "github.com/dk-lockdown/seata-golang/util"
+ "sort"
+ "sync"
+ "vimagination.zapto.org/byteio"
+)
+
+type GlobalSession struct {
+ sync.Mutex
+
+ Xid string
+
+ TransactionId int64
+
+ Status meta.GlobalStatus
+
+ ApplicationId string
+
+ TransactionServiceGroup string
+
+ TransactionName string
+
+ Timeout int32
+
+ BeginTime int64
+
+ ApplicationData []byte
+
+ Active bool
+
+ BranchSessions map[*BranchSession]bool
+}
+
+func NewGlobalSession() *GlobalSession {
+ gs := &GlobalSession{
+ BranchSessions: make(map[*BranchSession]bool),
+ }
+ gs.TransactionId = util.GeneratorUUID()
+ gs.Xid = common.XID.GenerateXID(gs.TransactionId)
+ return gs
+}
+
+func (gs *GlobalSession) SetXid(xid string) *GlobalSession {
+ gs.Xid = xid
+ return gs
+}
+
+func (gs *GlobalSession) SetTransactionId(transactionId int64) *GlobalSession {
+ gs.TransactionId = transactionId
+ return gs
+}
+
+func (gs *GlobalSession) SetStatus(status meta.GlobalStatus) *GlobalSession {
+ gs.Status = status
+ return gs
+}
+
+func (gs *GlobalSession) SetApplicationId(applicationId string) *GlobalSession {
+ gs.ApplicationId = applicationId
+ return gs
+}
+
+func (gs *GlobalSession) SetTransactionServiceGroup(transactionServiceGroup string) *GlobalSession {
+ gs.TransactionServiceGroup = transactionServiceGroup
+ return gs
+}
+
+func (gs *GlobalSession) SetTransactionName(transactionName string) *GlobalSession {
+ gs.TransactionName = transactionName
+ return gs
+}
+
+func (gs *GlobalSession) SetTimeout(timeout int32) *GlobalSession {
+ gs.Timeout = timeout
+ return gs
+}
+
+func (gs *GlobalSession) SetBeginTime(beginTime int64) *GlobalSession {
+ gs.BeginTime = beginTime
+ return gs
+}
+
+func (gs *GlobalSession) SetApplicationData(applicationData []byte) *GlobalSession {
+ gs.ApplicationData = applicationData
+ return gs
+}
+
+func (gs *GlobalSession) SetActive(active bool) *GlobalSession {
+ gs.Active = active
+ return gs
+}
+
+func (gs *GlobalSession) Add(branchSession *BranchSession) {
+ branchSession.Status = meta.BranchStatusRegistered
+ gs.BranchSessions[branchSession] = true
+}
+
+func (gs *GlobalSession) Remove(branchSession *BranchSession) {
+ delete(gs.BranchSessions, branchSession)
+}
+
+func (gs *GlobalSession) CanBeCommittedAsync() bool {
+ for branchSession := range gs.BranchSessions {
+ if branchSession.BranchType == meta.BranchTypeTCC {
+ return false
+ }
+ }
+ return true
+}
+
+func (gs *GlobalSession) IsSaga() bool {
+ for branchSession := range gs.BranchSessions {
+ if branchSession.BranchType == meta.BranchTypeSAGA {
+ return true
+ } else {
+ return false
+ }
+ }
+ return false
+}
+
+func (gs *GlobalSession) IsTimeout() bool {
+ return (util.CurrentTimeMillis() - uint64(gs.BeginTime)) > uint64(gs.Timeout)
+}
+
+func (gs *GlobalSession) IsRollbackingDead() bool {
+ return (util.CurrentTimeMillis() - uint64(gs.BeginTime)) > uint64(2 * 6000)
+}
+
+func (gs *GlobalSession) GetSortedBranches() []*BranchSession {
+ var branchSessions = make([]*BranchSession, 0)
+
+ for branchSession := range gs.BranchSessions {
+ branchSessions = append(branchSessions, branchSession)
+ }
+ return branchSessions
+}
+
+func (gs *GlobalSession) GetReverseSortedBranches() []*BranchSession {
+ var branchSessions = gs.GetSortedBranches()
+ sort.Reverse(BranchSessionSlice(branchSessions))
+ return branchSessions
+}
+
+type BranchSessionSlice []*BranchSession
+
+func (p BranchSessionSlice) Len() int { return len(p) }
+func (p BranchSessionSlice) Less(i, j int) bool { return p[i].CompareTo(p[j]) > 0 }
+func (p BranchSessionSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+func (gs *GlobalSession) GetBranch(branchId int64) *BranchSession {
+ gs.Lock()
+ defer gs.Unlock()
+ for branchSession := range gs.BranchSessions {
+ if branchSession.BranchId == branchId {
+ return branchSession
+ }
+ }
+ return nil
+}
+
+func (gs *GlobalSession) HasBranch() bool {
+ return len(gs.BranchSessions) > 0
+}
+
+func (gs *GlobalSession) Begin() {
+ gs.Status = meta.GlobalStatusBegin
+ gs.BeginTime = int64(util.CurrentTimeMillis())
+ gs.Active = true
+}
+
+func (gs *GlobalSession) Encode() ([]byte, error) {
+ var (
+ zero32 int32 = 0
+ zero16 int16 = 0
+ )
+
+ size := calGlobalSessionSize(len(gs.ApplicationId),len(gs.TransactionServiceGroup),len(gs.TransactionName),len(gs.Xid),len(gs.ApplicationData))
+
+ if size > config.GetStoreConfig().MaxGlobalSessionSize {
+ logging.Logger.Errorf("global session size exceeded, size : %d maxBranchSessionSize : %d", size, config.GetStoreConfig().MaxGlobalSessionSize)
+ //todo compress
+ return nil, errors.New("global session size exceeded.")
+ }
+
+ var b bytes.Buffer
+ w := byteio.BigEndianWriter{Writer: &b}
+
+ w.WriteInt64(gs.TransactionId)
+ w.WriteInt32(gs.Timeout)
+
+ // applicationId 长度不会超过 256
+ if gs.ApplicationId != "" {
+ w.WriteUint16(uint16(len(gs.ApplicationId)))
+ w.WriteString(gs.ApplicationId)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if gs.TransactionServiceGroup != "" {
+ w.WriteUint16(uint16(len(gs.TransactionServiceGroup)))
+ w.WriteString(gs.TransactionServiceGroup)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if gs.TransactionName != "" {
+ w.WriteUint16(uint16(len(gs.TransactionName)))
+ w.WriteString(gs.TransactionName)
+ } else {
+ w.WriteInt16(zero16)
+ }
+
+ if gs.Xid != "" {
+ w.WriteUint32(uint32(len(gs.Xid)))
+ w.WriteString(gs.Xid)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ if gs.ApplicationData != nil {
+ w.WriteUint32(uint32(len(gs.ApplicationData)))
+ w.Write(gs.ApplicationData)
+ } else {
+ w.WriteInt32(zero32)
+ }
+
+ w.WriteInt64(gs.BeginTime)
+ w.WriteByte(byte(gs.Status))
+
+ return b.Bytes(), nil
+}
+
+func (gs *GlobalSession) Decode(b []byte) {
+ var length32 uint32 = 0
+ var length16 uint16 = 0
+ r := byteio.BigEndianReader{Reader:bytes.NewReader(b)}
+
+ gs.TransactionId, _, _ = r.ReadInt64()
+ gs.Timeout, _, _ = r.ReadInt32()
+
+ length16, _, _ = r.ReadUint16()
+ if length16 > 0 { gs.ApplicationId, _, _ = r.ReadString(int(length16)) }
+
+ length16, _, _ = r.ReadUint16()
+ if length16 > 0 { gs.TransactionServiceGroup, _, _ = r.ReadString(int(length16)) }
+
+ length16, _, _ = r.ReadUint16()
+ if length16 > 0 { gs.TransactionName, _, _ = r.ReadString(int(length16)) }
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 { gs.Xid, _, _ = r.ReadString(int(length32)) }
+
+ length32, _, _ = r.ReadUint32()
+ if length32 > 0 {
+ gs.ApplicationData = make([]byte,length32,length32)
+ r.Read(gs.ApplicationData)
+ }
+
+ gs.BeginTime, _, _ = r.ReadInt64()
+
+ status, _ := r.ReadByte()
+ gs.Status = meta.GlobalStatus(status)
+}
+
+func calGlobalSessionSize( applicationIdLen int,
+ serviceGroupLen int,
+ txNameLen int,
+ xidLen int,
+ applicationDataLen int,
+ ) int{
+
+ size := 8 + // transactionId
+ 4 + // timeout
+ 2 + // byApplicationIdBytes.length
+ 2 + // byServiceGroupBytes.length
+ 2 + // byTxNameBytes.length
+ 4 + // xidBytes.length
+ 4 + // applicationDataBytes.length
+ 8 + // beginTime
+ 1 + // statusCode
+ applicationIdLen +
+ serviceGroupLen +
+ txNameLen +
+ xidLen +
+ applicationDataLen
+
+ return size
+}
\ No newline at end of file
diff --git a/tc/session/global_session_test.go b/tc/session/global_session_test.go
new file mode 100644
index 0000000..d28e936
--- /dev/null
+++ b/tc/session/global_session_test.go
@@ -0,0 +1,31 @@
+package session
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestGlobalSession_Encode_Decode(t *testing.T) {
+ gs := globalSessionProvider()
+ result, _ := gs.Encode()
+
+ newGs := &GlobalSession{}
+ newGs.Decode(result)
+
+ assert.Equal(t,newGs.TransactionId,gs.TransactionId)
+ assert.Equal(t,newGs.Timeout,gs.Timeout)
+ assert.Equal(t,newGs.ApplicationId,gs.ApplicationId)
+ assert.Equal(t,newGs.TransactionServiceGroup,gs.TransactionServiceGroup)
+ assert.Equal(t,newGs.TransactionName,gs.TransactionName)
+}
+
+func globalSessionProvider() *GlobalSession{
+ gs := NewGlobalSession().
+ SetApplicationId("demo-app").
+ SetTransactionServiceGroup("my_test_tx_group").
+ SetTransactionName("test").
+ SetTimeout(6000).
+ SetActive(true)
+
+ return gs
+}
\ No newline at end of file
diff --git a/tc/session/session_storable.go b/tc/session/session_storable.go
new file mode 100644
index 0000000..b2a8b45
--- /dev/null
+++ b/tc/session/session_storable.go
@@ -0,0 +1,17 @@
+package session
+
+type SessionStorable interface {
+ /**
+ * Encode byte [ ].
+ *
+ * @return the byte [ ]
+ */
+ Encode() ([]byte, error)
+
+ /**
+ * Decode.
+ *
+ * @param src the src
+ */
+ Decode(src []byte)
+}
diff --git a/tm/transaction_manager.go b/tm/transaction_manager.go
new file mode 100644
index 0000000..635444e
--- /dev/null
+++ b/tm/transaction_manager.go
@@ -0,0 +1,59 @@
+package tm
+
+import "github.com/dk-lockdown/seata-golang/meta"
+
+type ITransactionManager interface {
+ /**
+ * GlobalStatus_Begin a new global transaction.
+ *
+ * @param applicationId ID of the application who begins this transaction.
+ * @param transactionServiceGroup ID of the transaction service group.
+ * @param name Give a name to the global transaction.
+ * @param timeout Timeout of the global transaction.
+ * @return XID of the global transaction
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ Begin(applicationId string, transactionServiceGroup string, name string, timeout int32) (string, error)
+
+ /**
+ * Global commit.
+ *
+ * @param xid XID of the global transaction.
+ * @return Status of the global transaction after committing.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ Commit(xid string) (meta.GlobalStatus, error)
+
+ /**
+ * Global rollback.
+ *
+ * @param xid XID of the global transaction
+ * @return Status of the global transaction after rollbacking.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ Rollback(xid string) (meta.GlobalStatus, error)
+
+ /**
+ * Get current status of the give transaction.
+ *
+ * @param xid XID of the global transaction.
+ * @return Current status of the global transaction.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ GetStatus(xid string) (meta.GlobalStatus, error)
+
+ /**
+ * Global report.
+ *
+ * @param xid XID of the global transaction.
+ * @param globalStatus Status of the global transaction.
+ * @return Status of the global transaction.
+ * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown
+ * out.
+ */
+ GlobalReport(xid string, globalStatus meta.GlobalStatus) (meta.GlobalStatus, error)
+}
diff --git a/util/hashcode.go b/util/hashcode.go
new file mode 100644
index 0000000..00055f0
--- /dev/null
+++ b/util/hashcode.go
@@ -0,0 +1,35 @@
+package util
+
+import (
+ "bytes"
+ "fmt"
+ "hash/crc32"
+)
+
+// String hashes a string to a unique hashcode.
+//
+// crc32 returns a uint32, but for our use we need
+// and non negative integer. Here we cast to an integer
+// and invert it if the result is negative.
+func String(s string) int {
+ v := int(crc32.ChecksumIEEE([]byte(s)))
+ if v >= 0 {
+ return v
+ }
+ if -v >= 0 {
+ return -v
+ }
+ // v == MinInt
+ return 0
+}
+
+// Strings hashes a list of strings to a unique hashcode.
+func Strings(strings []string) string {
+ var buf bytes.Buffer
+
+ for _, s := range strings {
+ buf.WriteString(fmt.Sprintf("%s-", s))
+ }
+
+ return fmt.Sprintf("%d", String(buf.String()))
+}
diff --git a/util/time.go b/util/time.go
new file mode 100644
index 0000000..8641936
--- /dev/null
+++ b/util/time.go
@@ -0,0 +1,33 @@
+package util
+
+import (
+ "time"
+)
+
+const (
+ TimeFormat = "2006-01-02 15:04:05"
+ DateFormat = "2006-01-02"
+ UnixTimeUnitOffset = uint64(time.Millisecond / time.Nanosecond)
+)
+
+// FormatTimeMillis converts Millisecond to time string
+// tsMillis accurates to millisecond,otherwise, an exception will occur
+func FormatTimeMillis(tsMillis uint64) string {
+ return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(TimeFormat)
+}
+
+// FormatDate converts to date string
+// tsMillis accurates to millisecond,otherwise, an exception will occur
+func FormatDate(tsMillis uint64) string {
+ return time.Unix(0, int64(tsMillis*UnixTimeUnitOffset)).Format(DateFormat)
+}
+
+// Returns the current Unix timestamp in milliseconds.
+func CurrentTimeMillis() uint64 {
+ return uint64(time.Now().UnixNano()) / UnixTimeUnitOffset
+}
+
+// Returns the current Unix timestamp in nanoseconds.
+func CurrentTimeNano() uint64 {
+ return uint64(time.Now().UnixNano())
+}
diff --git a/util/uuid_generator.go b/util/uuid_generator.go
new file mode 100644
index 0000000..ffb7871
--- /dev/null
+++ b/util/uuid_generator.go
@@ -0,0 +1,46 @@
+package util
+
+import (
+ "sync/atomic"
+)
+
+var (
+ UUID int64 = 1000
+ serverNodeId = 1
+ UUID_INTERNAL int64 = 2000000000
+ initUUID int64 = 0
+)
+
+func GeneratorUUID() int64 {
+ id := atomic.AddInt64(&UUID,1)
+ if id >= getMaxUUID() {
+ if UUID >= id {
+ newId := id - UUID_INTERNAL
+ atomic.CompareAndSwapInt64(&UUID,id, newId)
+ return newId
+ }
+ }
+ return id
+}
+
+func SetUUID(expect int64, update int64) bool {
+ return atomic.CompareAndSwapInt64(&UUID,expect, update)
+}
+
+func getMaxUUID() int64 {
+ return UUID_INTERNAL * (int64(serverNodeId) +1)
+}
+
+func GetInitUUID() int64 {
+ return initUUID
+}
+
+func Init(svrNodeId int) {
+ // 2019-01-01 与 java 版 seata 一致
+ var base uint64 = 1546272000000
+ serverNodeId = svrNodeId
+ atomic.CompareAndSwapInt64(&UUID,UUID,UUID_INTERNAL*int64(serverNodeId))
+ current := CurrentTimeMillis()
+ id := atomic.AddInt64(&UUID, int64((current - base)/UnixTimeUnitOffset))
+ initUUID = id
+}
\ No newline at end of file