* feat: add grpc invoker in saga mode * fix: fix some chorespull/669/head
@@ -34,6 +34,7 @@ require ( | |||
github.com/agiledragon/gomonkey v2.0.2+incompatible | |||
github.com/agiledragon/gomonkey/v2 v2.9.0 | |||
github.com/mattn/go-sqlite3 v1.14.19 | |||
google.golang.org/protobuf v1.30.0 | |||
) | |||
require ( | |||
@@ -86,7 +87,6 @@ require ( | |||
go.uber.org/multierr v1.8.0 // indirect | |||
golang.org/x/arch v0.3.0 // indirect | |||
golang.org/x/text v0.14.0 // indirect | |||
google.golang.org/protobuf v1.30.0 // indirect | |||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect | |||
gopkg.in/yaml.v3 v3.0.1 // indirect | |||
) | |||
@@ -0,0 +1,244 @@ | |||
package invoker | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"github.com/seata/seata-go/pkg/util/log" | |||
"google.golang.org/grpc" | |||
"reflect" | |||
"strings" | |||
"sync" | |||
"time" | |||
) | |||
type GRPCInvoker struct { | |||
clients map[string]GRPCClient | |||
clientsMapLock sync.Mutex | |||
needClose bool | |||
} | |||
func NewGRPCInvoker() *GRPCInvoker { | |||
return &GRPCInvoker{ | |||
clients: make(map[string]GRPCClient), | |||
} | |||
} | |||
func (g *GRPCInvoker) NeedClose() bool { | |||
return g.needClose | |||
} | |||
func (g *GRPCInvoker) SetNeedClose(needClose bool) { | |||
g.needClose = needClose | |||
} | |||
func (g *GRPCInvoker) RegisterClient(serviceName string, client GRPCClient) { | |||
g.clientsMapLock.Lock() | |||
defer g.clientsMapLock.Unlock() | |||
g.clients[serviceName] = client | |||
} | |||
func (g *GRPCInvoker) GetClient(serviceName string) GRPCClient { | |||
g.clientsMapLock.Lock() | |||
defer g.clientsMapLock.Unlock() | |||
if client, ok := g.clients[serviceName]; ok { | |||
return client | |||
} | |||
return nil | |||
} | |||
func (g *GRPCInvoker) Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) { | |||
serviceTaskStateImpl := service.(*state.ServiceTaskStateImpl) | |||
client := g.GetClient(serviceTaskStateImpl.ServiceName()) | |||
if client == nil { | |||
return nil, errors.New(fmt.Sprintf("no grpc client %s for service task state", serviceTaskStateImpl.ServiceName())) | |||
} | |||
// context is the first arg in grpc client method | |||
input = append([]any{ctx}, input...) | |||
if serviceTaskStateImpl.IsAsync() { | |||
go func() { | |||
_, err := client.CallMethod(serviceTaskStateImpl, input) | |||
if err != nil { | |||
log.Errorf("invoke Service[%s].%s failed, err is %s", serviceTaskStateImpl.ServiceName(), | |||
serviceTaskStateImpl.ServiceMethod(), err) | |||
} | |||
}() | |||
return nil, nil | |||
} else { | |||
return client.CallMethod(serviceTaskStateImpl, input) | |||
} | |||
} | |||
func (g *GRPCInvoker) Close(ctx context.Context) error { | |||
if g.needClose { | |||
for _, client := range g.clients { | |||
err := client.CloseConnection() | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
type GRPCClient interface { | |||
CallMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) | |||
CloseConnection() error | |||
} | |||
type GPRCClientImpl struct { | |||
serviceName string | |||
client any | |||
connection *grpc.ClientConn | |||
methodLock sync.Mutex | |||
} | |||
func NewGRPCClient(serviceName string, client any, connection *grpc.ClientConn) *GPRCClientImpl { | |||
return &GPRCClientImpl{ | |||
serviceName: serviceName, | |||
client: client, | |||
connection: connection, | |||
} | |||
} | |||
func (g *GPRCClientImpl) CallMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl, input []any) ([]reflect.Value, error) { | |||
if serviceTaskStateImpl.Method() == nil { | |||
err := g.initMethod(serviceTaskStateImpl) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
method := serviceTaskStateImpl.Method() | |||
args := make([]reflect.Value, 0, len(input)) | |||
for _, arg := range input { | |||
args = append(args, reflect.ValueOf(arg)) | |||
} | |||
retryCountMap := make(map[state.Retry]int) | |||
for { | |||
res, err, shouldRetry := func() (res []reflect.Value, resErr error, shouldRetry bool) { | |||
defer func() { | |||
// err may happen in the method invoke (panic) and method return, we try to find err and use it to decide retry by | |||
// whether contains exception or not | |||
if r := recover(); r != nil { | |||
errStr := fmt.Sprintf("%v", r) | |||
retry := g.matchRetry(serviceTaskStateImpl, errStr) | |||
res = nil | |||
resErr = errors.New(errStr) | |||
if retry == nil { | |||
return | |||
} | |||
shouldRetry = g.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||
return | |||
} | |||
}() | |||
outs := method.Call(args) | |||
// err is the last arg in grpc client method | |||
if err, ok := outs[len(outs)-1].Interface().(error); ok { | |||
errStr := err.Error() | |||
retry := g.matchRetry(serviceTaskStateImpl, errStr) | |||
res = nil | |||
resErr = err | |||
if retry == nil { | |||
return | |||
} | |||
shouldRetry = g.needRetry(serviceTaskStateImpl, retryCountMap, retry, resErr) | |||
return | |||
} | |||
// invoke success | |||
res = outs | |||
resErr = nil | |||
shouldRetry = false | |||
return | |||
}() | |||
if !shouldRetry { | |||
if err != nil { | |||
return nil, errors.New(fmt.Sprintf("invoke Service[%s] failed, not satisfy retry config, the last err is %s", | |||
serviceTaskStateImpl.ServiceName(), err)) | |||
} | |||
return res, nil | |||
} | |||
} | |||
} | |||
func (g *GPRCClientImpl) CloseConnection() error { | |||
if g.connection != nil { | |||
err := g.connection.Close() | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func (g *GPRCClientImpl) initMethod(serviceTaskStateImpl *state.ServiceTaskStateImpl) error { | |||
methodName := serviceTaskStateImpl.ServiceMethod() | |||
g.methodLock.Lock() | |||
defer g.methodLock.Unlock() | |||
clientValue := reflect.ValueOf(g.client) | |||
if clientValue.IsZero() { | |||
return errors.New(fmt.Sprintf("invalid client value when grpc client call, serviceName: %s", g.serviceName)) | |||
} | |||
method := clientValue.MethodByName(methodName) | |||
if method.IsZero() { | |||
return errors.New(fmt.Sprintf("invalid client method when grpc client call, serviceName: %s, serviceMethod: %s", | |||
g.serviceName, methodName)) | |||
} | |||
serviceTaskStateImpl.SetMethod(&method) | |||
return nil | |||
} | |||
func (g *GPRCClientImpl) matchRetry(impl *state.ServiceTaskStateImpl, str string) state.Retry { | |||
if impl.Retry() != nil { | |||
for _, retry := range impl.Retry() { | |||
if retry.Exceptions() != nil { | |||
for _, exception := range retry.Exceptions() { | |||
if strings.Contains(str, exception) { | |||
return retry | |||
} | |||
} | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
func (g *GPRCClientImpl) needRetry(impl *state.ServiceTaskStateImpl, countMap map[state.Retry]int, retry state.Retry, err error) bool { | |||
attempt, exist := countMap[retry] | |||
if !exist { | |||
countMap[retry] = 0 | |||
} | |||
if attempt >= retry.MaxAttempt() { | |||
return false | |||
} | |||
intervalSecond := retry.IntervalSecond() | |||
backoffRate := retry.BackoffRate() | |||
var currentInterval int64 | |||
if attempt == 0 { | |||
currentInterval = int64(intervalSecond * 1000) | |||
} else { | |||
currentInterval = int64(intervalSecond * backoffRate * float64(attempt) * 1000) | |||
} | |||
log.Warnf("invoke service[%s.%s] failed, will retry after %s millis, current retry count: %s, current err: %s", | |||
impl.ServiceName(), impl.ServiceMethod(), currentInterval, attempt, err) | |||
time.Sleep(time.Duration(currentInterval) * time.Millisecond) | |||
countMap[retry] = attempt + 1 | |||
return true | |||
} |
@@ -0,0 +1,168 @@ | |||
package invoker | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"github.com/stretchr/testify/assert" | |||
"google.golang.org/grpc" | |||
"google.golang.org/grpc/credentials/insecure" | |||
"testing" | |||
"time" | |||
pb "github.com/seata/seata-go/testdata/saga/engine/invoker/grpc" | |||
) | |||
type MockGPRCClientImpl struct { | |||
GPRCClientImpl | |||
} | |||
type mockClientImpl struct { | |||
invokeCount int | |||
} | |||
func (m *mockClientImpl) SayHelloRight(ctx context.Context, word string) (string, error) { | |||
m.invokeCount++ | |||
fmt.Println("invoke right") | |||
return word, nil | |||
} | |||
func (m *mockClientImpl) SayHelloRightLater(ctx context.Context, word string, delay int) (string, error) { | |||
m.invokeCount++ | |||
if delay == m.invokeCount { | |||
fmt.Println("invoke right") | |||
return word, nil | |||
} | |||
fmt.Println("invoke fail") | |||
return "", errors.New("invoke failed") | |||
} | |||
func TestGRPCInvokerInvokeSucceedWithOutRetry(t *testing.T) { | |||
ctx := context.Background() | |||
invoker := newGRPCServiceInvoker() | |||
values, err := invoker.Invoke(ctx, []any{"hello"}, newHelloServiceTaskState()) | |||
if err != nil { | |||
t.Error(err) | |||
return | |||
} | |||
if values == nil || len(values) == 0 { | |||
t.Error("no value in values") | |||
return | |||
} | |||
if values[0].Interface().(string) != "hello" { | |||
t.Errorf("expect hello, but got %v", values[0].Interface()) | |||
} | |||
if _, ok := values[1].Interface().(error); ok { | |||
t.Errorf("expect nil, but got %v", values[1].Interface()) | |||
} | |||
} | |||
func TestGRPCInvokerInvokeSucceedInRetry(t *testing.T) { | |||
ctx := context.Background() | |||
invoker := newGRPCServiceInvoker() | |||
values, err := invoker.Invoke(ctx, []any{"hello", 2}, newHelloServiceTaskStateWithRetry()) | |||
if err != nil { | |||
t.Error(err) | |||
return | |||
} | |||
if values == nil || len(values) == 0 { | |||
t.Error("no value in values") | |||
return | |||
} | |||
if values[0].Interface().(string) != "hello" { | |||
t.Errorf("expect hello, but got %v", values[0].Interface()) | |||
} | |||
if _, ok := values[1].Interface().(error); ok { | |||
t.Errorf("expect nil, but got %v", values[1].Interface()) | |||
} | |||
} | |||
func TestGRPCInvokerInvokeFailedInRetry(t *testing.T) { | |||
ctx := context.Background() | |||
invoker := newGRPCServiceInvoker() | |||
_, err := invoker.Invoke(ctx, []any{"hello", 5}, newHelloServiceTaskStateWithRetry()) | |||
if err != nil { | |||
assert.Error(t, err) | |||
} | |||
} | |||
func TestGRPCInvokerInvokeE2E(t *testing.T) { | |||
go func() { | |||
pb.StartProductServer() | |||
}() | |||
time.Sleep(3000 * time.Millisecond) | |||
conn, err := grpc.Dial("localhost:8080", grpc.WithTransportCredentials(insecure.NewCredentials())) | |||
if err != nil { | |||
t.Fatalf("did not connect: %v", err) | |||
} | |||
c := pb.NewProductInfoClient(conn) | |||
grpcClient := NewGRPCClient("product", c, conn) | |||
invoker := NewGRPCInvoker() | |||
invoker.RegisterClient("product", grpcClient) | |||
ctx := context.Background() | |||
values, err := invoker.Invoke(ctx, []any{&pb.Product{Id: "123"}}, newProductServiceTaskState()) | |||
if err != nil { | |||
t.Error(err) | |||
return | |||
} | |||
t.Log(values) | |||
err = invoker.Close(ctx) | |||
if err != nil { | |||
t.Error(err) | |||
return | |||
} | |||
} | |||
func newGRPCServiceInvoker() ServiceInvoker { | |||
mockGRPCInvoker := NewGRPCInvoker() | |||
mockGRPCClient := &mockClientImpl{} | |||
mockClient := NewGRPCClient("hello", mockGRPCClient, &grpc.ClientConn{}) | |||
mockGRPCInvoker.RegisterClient("hello", mockClient) | |||
return mockGRPCInvoker | |||
} | |||
func newProductServiceTaskState() state.ServiceTaskState { | |||
serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||
serviceTaskStateImpl.SetName("product") | |||
serviceTaskStateImpl.SetIsAsync(false) | |||
serviceTaskStateImpl.SetServiceName("product") | |||
serviceTaskStateImpl.SetServiceType("GRPC") | |||
serviceTaskStateImpl.SetServiceMethod("AddProduct") | |||
retryImpl := &state.RetryImpl{} | |||
retryImpl.SetExceptions([]string{"fail"}) | |||
retryImpl.SetIntervalSecond(1) | |||
retryImpl.SetMaxAttempt(3) | |||
retryImpl.SetBackoffRate(0.9) | |||
serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||
return serviceTaskStateImpl | |||
} | |||
func newHelloServiceTaskState() state.ServiceTaskState { | |||
serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||
serviceTaskStateImpl.SetName("hello") | |||
serviceTaskStateImpl.SetIsAsync(false) | |||
serviceTaskStateImpl.SetServiceName("hello") | |||
serviceTaskStateImpl.SetServiceType("GRPC") | |||
serviceTaskStateImpl.SetServiceMethod("SayHelloRight") | |||
return serviceTaskStateImpl | |||
} | |||
func newHelloServiceTaskStateWithRetry() state.ServiceTaskState { | |||
serviceTaskStateImpl := state.NewServiceTaskStateImpl() | |||
serviceTaskStateImpl.SetName("hello") | |||
serviceTaskStateImpl.SetIsAsync(false) | |||
serviceTaskStateImpl.SetServiceName("hello") | |||
serviceTaskStateImpl.SetServiceType("GRPC") | |||
serviceTaskStateImpl.SetServiceMethod("SayHelloRightLater") | |||
retryImpl := &state.RetryImpl{} | |||
retryImpl.SetExceptions([]string{"fail"}) | |||
retryImpl.SetIntervalSecond(1) | |||
retryImpl.SetMaxAttempt(3) | |||
retryImpl.SetBackoffRate(0.9) | |||
serviceTaskStateImpl.SetRetry([]state.Retry{retryImpl}) | |||
return serviceTaskStateImpl | |||
} |
@@ -1,5 +1,12 @@ | |||
package invoker | |||
import ( | |||
"context" | |||
"github.com/seata/seata-go/pkg/saga/statemachine/statelang/state" | |||
"reflect" | |||
"sync" | |||
) | |||
type ScriptInvokerManager interface { | |||
} | |||
@@ -7,8 +14,32 @@ type ScriptInvoker interface { | |||
} | |||
type ServiceInvokerManager interface { | |||
ServiceInvoker(serviceType string) ServiceInvoker | |||
PutServiceInvoker(serviceType string, invoker ServiceInvoker) | |||
} | |||
type ServiceInvoker interface { | |||
Invoke() | |||
Invoke(ctx context.Context, input []any, service state.ServiceTaskState) (output []reflect.Value, err error) | |||
Close(ctx context.Context) error | |||
} | |||
type ServiceInvokerManagerImpl struct { | |||
invokers map[string]ServiceInvoker | |||
mutex sync.Mutex | |||
} | |||
func NewServiceInvokerManagerImpl() *ServiceInvokerManagerImpl { | |||
return &ServiceInvokerManagerImpl{ | |||
invokers: make(map[string]ServiceInvoker), | |||
} | |||
} | |||
func (manager *ServiceInvokerManagerImpl) ServiceInvoker(serviceType string) ServiceInvoker { | |||
return manager.invokers[serviceType] | |||
} | |||
func (manager *ServiceInvokerManagerImpl) PutServiceInvoker(serviceType string, invoker ServiceInvoker) { | |||
manager.mutex.Lock() | |||
defer manager.mutex.Unlock() | |||
manager.invokers[serviceType] = invoker | |||
} |
@@ -136,7 +136,7 @@ func (b BaseStateParser) GetBoolOrDefault(stateName string, stateMap map[string] | |||
valueAsBool, ok := value.(bool) | |||
if !ok { | |||
return false, errors.New("State [" + stateName + "] " + key + " illegal, required bool") | |||
return defaultValue, errors.New("State [" + stateName + "] " + key + " illegal, required bool") | |||
} | |||
return valueAsBool, nil | |||
} | |||
@@ -157,16 +157,16 @@ func (a *AbstractTaskStateParser) parseRetries(stateName string, retryInterfaces | |||
return nil, errors.New("State [" + stateName + "] " + "Retry illegal, require map[string]interface{}") | |||
} | |||
retry := &state.RetryImpl{} | |||
errorTypes, err := a.GetSliceOrDefault(stateName, retryMap, "Exceptions", nil) | |||
exceptions, err := a.GetSliceOrDefault(stateName, retryMap, "Exceptions", nil) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if errorTypes != nil { | |||
errorTypeNames := make([]string, 0) | |||
for _, errorType := range errorTypes { | |||
errorTypeNames = append(errorTypeNames, errorType.(string)) | |||
if exceptions != nil { | |||
errors := make([]string, 0) | |||
for _, errorType := range exceptions { | |||
errors = append(errors, errorType.(string)) | |||
} | |||
retry.SetErrorTypeNames(errorTypeNames) | |||
retry.SetExceptions(errors) | |||
} | |||
maxAttempts, err := a.GetIntOrDefault(stateName, retryMap, "MaxAttempts", 0) | |||
@@ -191,14 +191,14 @@ func (a *AbstractTaskStateParser) parseRetries(stateName string, retryInterfaces | |||
return retries, nil | |||
} | |||
func (a *AbstractTaskStateParser) parseCatches(stateName string, catchInterfaces []interface{}) ([]state.ErrorMatch, error) { | |||
errorMatches := make([]state.ErrorMatch, 0, len(catchInterfaces)) | |||
func (a *AbstractTaskStateParser) parseCatches(stateName string, catchInterfaces []interface{}) ([]state.ExceptionMatch, error) { | |||
errorMatches := make([]state.ExceptionMatch, 0, len(catchInterfaces)) | |||
for _, catchInterface := range catchInterfaces { | |||
catchMap, ok := catchInterface.(map[string]interface{}) | |||
if !ok { | |||
return nil, errors.New("State [" + stateName + "] " + "Catch illegal, require map[string]interface{}") | |||
} | |||
errorMatch := &state.ErrorMatchImpl{} | |||
errorMatch := &state.ExceptionMatchImpl{} | |||
errorInterfaces, err := a.GetSliceOrDefault(stateName, catchMap, "Exceptions", nil) | |||
if err != nil { | |||
return nil, err | |||
@@ -208,7 +208,7 @@ func (a *AbstractTaskStateParser) parseCatches(stateName string, catchInterfaces | |||
for _, errorType := range errorInterfaces { | |||
errorNames = append(errorNames, errorType.(string)) | |||
} | |||
errorMatch.SetErrors(errorNames) | |||
errorMatch.SetExceptions(errorNames) | |||
} | |||
next, err := a.GetStringOrDefault(stateName, catchMap, "Next", "") | |||
if err != nil { | |||
@@ -242,7 +242,7 @@ func (s ServiceTaskStateParser) Parse(stateName string, stateMap map[string]inte | |||
return nil, err | |||
} | |||
serviceName, err := s.GetString(stateName, stateMap, "ServiceName") | |||
serviceName, err := s.GetString(stateName, stateMap, "serviceName") | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -17,7 +17,7 @@ type TaskState interface { | |||
Retry() []Retry | |||
Catches() []ErrorMatch | |||
Catches() []ExceptionMatch | |||
Status() map[string]string | |||
@@ -36,18 +36,18 @@ type Loop interface { | |||
CompletionCondition() string | |||
} | |||
type ErrorMatch interface { | |||
Errors() []string | |||
type ExceptionMatch interface { | |||
Exceptions() []string | |||
ErrorTypes() []reflect.Type | |||
ExceptionTypes() []reflect.Type | |||
SetErrorTypes(errorTypes []reflect.Type) | |||
SetExceptionTypes(ExceptionTypes []reflect.Type) | |||
Next() string | |||
} | |||
type Retry interface { | |||
ErrorTypeNames() []string | |||
Exceptions() []string | |||
IntervalSecond() float64 | |||
@@ -77,7 +77,7 @@ type ServiceTaskState interface { | |||
type AbstractTaskState struct { | |||
*statelang.BaseState | |||
loop Loop | |||
catches []ErrorMatch | |||
catches []ExceptionMatch | |||
input []interface{} | |||
output map[string]interface{} | |||
compensatePersistModeUpdate bool | |||
@@ -140,7 +140,7 @@ func (a *AbstractTaskState) SetLoop(loop Loop) { | |||
a.loop = loop | |||
} | |||
func (a *AbstractTaskState) SetCatches(catches []ErrorMatch) { | |||
func (a *AbstractTaskState) SetCatches(catches []ExceptionMatch) { | |||
a.catches = catches | |||
} | |||
@@ -172,7 +172,7 @@ func (a *AbstractTaskState) ForUpdate() bool { | |||
return a.forUpdate | |||
} | |||
func (a *AbstractTaskState) Catches() []ErrorMatch { | |||
func (a *AbstractTaskState) Catches() []ExceptionMatch { | |||
return a.catches | |||
} | |||
@@ -198,6 +198,7 @@ type ServiceTaskStateImpl struct { | |||
serviceName string | |||
serviceMethod string | |||
parameterTypes []string | |||
method *reflect.Value | |||
persist bool | |||
retryPersistModeUpdate bool | |||
compensatePersistModeUpdate bool | |||
@@ -210,6 +211,14 @@ func NewServiceTaskStateImpl() *ServiceTaskStateImpl { | |||
} | |||
} | |||
func (s *ServiceTaskStateImpl) Method() *reflect.Value { | |||
return s.method | |||
} | |||
func (s *ServiceTaskStateImpl) SetMethod(method *reflect.Value) { | |||
s.method = method | |||
} | |||
func (s *ServiceTaskStateImpl) IsAsync() bool { | |||
return s.isAsync | |||
} | |||
@@ -327,14 +336,14 @@ func (l *LoopImpl) CompletionCondition() string { | |||
} | |||
type RetryImpl struct { | |||
errorTypeNames []string | |||
exceptions []string | |||
intervalSecond float64 | |||
maxAttempt int | |||
backoffRate float64 | |||
} | |||
func (r *RetryImpl) SetErrorTypeNames(errorTypeNames []string) { | |||
r.errorTypeNames = errorTypeNames | |||
func (r *RetryImpl) SetExceptions(exceptions []string) { | |||
r.exceptions = exceptions | |||
} | |||
func (r *RetryImpl) SetIntervalSecond(intervalSecond float64) { | |||
@@ -349,8 +358,8 @@ func (r *RetryImpl) SetBackoffRate(backoffRate float64) { | |||
r.backoffRate = backoffRate | |||
} | |||
func (r *RetryImpl) ErrorTypeNames() []string { | |||
return r.errorTypeNames | |||
func (r *RetryImpl) Exceptions() []string { | |||
return r.exceptions | |||
} | |||
func (r *RetryImpl) IntervalSecond() float64 { | |||
@@ -365,33 +374,33 @@ func (r *RetryImpl) BackoffRate() float64 { | |||
return r.backoffRate | |||
} | |||
type ErrorMatchImpl struct { | |||
errors []string | |||
errorTypes []reflect.Type | |||
next string | |||
type ExceptionMatchImpl struct { | |||
exceptions []string | |||
exceptionTypes []reflect.Type | |||
next string | |||
} | |||
func (e *ErrorMatchImpl) SetErrors(errors []string) { | |||
e.errors = errors | |||
func (e *ExceptionMatchImpl) SetExceptions(errors []string) { | |||
e.exceptions = errors | |||
} | |||
func (e *ErrorMatchImpl) SetNext(next string) { | |||
func (e *ExceptionMatchImpl) SetNext(next string) { | |||
e.next = next | |||
} | |||
func (e *ErrorMatchImpl) Errors() []string { | |||
return e.errors | |||
func (e *ExceptionMatchImpl) Exceptions() []string { | |||
return e.exceptions | |||
} | |||
func (e *ErrorMatchImpl) ErrorTypes() []reflect.Type { | |||
return e.errorTypes | |||
func (e *ExceptionMatchImpl) ExceptionTypes() []reflect.Type { | |||
return e.exceptionTypes | |||
} | |||
func (e *ErrorMatchImpl) SetErrorTypes(errorTypes []reflect.Type) { | |||
e.errorTypes = errorTypes | |||
func (e *ExceptionMatchImpl) SetExceptionTypes(exceptionTypes []reflect.Type) { | |||
e.exceptionTypes = exceptionTypes | |||
} | |||
func (e *ErrorMatchImpl) Next() string { | |||
func (e *ExceptionMatchImpl) Next() string { | |||
return e.next | |||
} | |||
@@ -0,0 +1,35 @@ | |||
package product | |||
import ( | |||
"context" | |||
"log" | |||
"net" | |||
"google.golang.org/grpc" | |||
) | |||
type server struct { | |||
UnimplementedProductInfoServer | |||
} | |||
func (*server) AddProduct(context.Context, *Product) (*ProductId, error) { | |||
log.Println("add product success") | |||
return &ProductId{Value: "1"}, nil | |||
} | |||
func (*server) GetProduct(context.Context, *ProductId) (*Product, error) { | |||
log.Println("get product success") | |||
return &Product{Id: "1"}, nil | |||
} | |||
func StartProductServer() { | |||
lis, err := net.Listen("tcp", ":8080") | |||
if err != nil { | |||
log.Fatalf("failed to listen: %v", err) | |||
} | |||
s := grpc.NewServer() | |||
RegisterProductInfoServer(s, &server{}) | |||
log.Printf("server listening at %v", lis.Addr()) | |||
if err := s.Serve(lis); err != nil { | |||
log.Fatalf("failed to serve: %v", err) | |||
} | |||
} |
@@ -0,0 +1,219 @@ | |||
// Code generated by protoc-gen-go. DO NOT EDIT. | |||
// versions: | |||
// protoc-gen-go v1.32.0 | |||
// protoc v4.25.3 | |||
// source: product.proto | |||
package product | |||
import ( | |||
protoreflect "google.golang.org/protobuf/reflect/protoreflect" | |||
protoimpl "google.golang.org/protobuf/runtime/protoimpl" | |||
reflect "reflect" | |||
sync "sync" | |||
) | |||
const ( | |||
// Verify that this generated code is sufficiently up-to-date. | |||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) | |||
// Verify that runtime/protoimpl is sufficiently up-to-date. | |||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) | |||
) | |||
type Product struct { | |||
state protoimpl.MessageState | |||
sizeCache protoimpl.SizeCache | |||
unknownFields protoimpl.UnknownFields | |||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` | |||
} | |||
func (x *Product) Reset() { | |||
*x = Product{} | |||
if protoimpl.UnsafeEnabled { | |||
mi := &file_product_proto_msgTypes[0] | |||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
ms.StoreMessageInfo(mi) | |||
} | |||
} | |||
func (x *Product) String() string { | |||
return protoimpl.X.MessageStringOf(x) | |||
} | |||
func (*Product) ProtoMessage() {} | |||
func (x *Product) ProtoReflect() protoreflect.Message { | |||
mi := &file_product_proto_msgTypes[0] | |||
if protoimpl.UnsafeEnabled && x != nil { | |||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
if ms.LoadMessageInfo() == nil { | |||
ms.StoreMessageInfo(mi) | |||
} | |||
return ms | |||
} | |||
return mi.MessageOf(x) | |||
} | |||
// Deprecated: Use Product.ProtoReflect.Descriptor instead. | |||
func (*Product) Descriptor() ([]byte, []int) { | |||
return file_product_proto_rawDescGZIP(), []int{0} | |||
} | |||
func (x *Product) GetId() string { | |||
if x != nil { | |||
return x.Id | |||
} | |||
return "" | |||
} | |||
type ProductId struct { | |||
state protoimpl.MessageState | |||
sizeCache protoimpl.SizeCache | |||
unknownFields protoimpl.UnknownFields | |||
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` | |||
} | |||
func (x *ProductId) Reset() { | |||
*x = ProductId{} | |||
if protoimpl.UnsafeEnabled { | |||
mi := &file_product_proto_msgTypes[1] | |||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
ms.StoreMessageInfo(mi) | |||
} | |||
} | |||
func (x *ProductId) String() string { | |||
return protoimpl.X.MessageStringOf(x) | |||
} | |||
func (*ProductId) ProtoMessage() {} | |||
func (x *ProductId) ProtoReflect() protoreflect.Message { | |||
mi := &file_product_proto_msgTypes[1] | |||
if protoimpl.UnsafeEnabled && x != nil { | |||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
if ms.LoadMessageInfo() == nil { | |||
ms.StoreMessageInfo(mi) | |||
} | |||
return ms | |||
} | |||
return mi.MessageOf(x) | |||
} | |||
// Deprecated: Use ProductId.ProtoReflect.Descriptor instead. | |||
func (*ProductId) Descriptor() ([]byte, []int) { | |||
return file_product_proto_rawDescGZIP(), []int{1} | |||
} | |||
func (x *ProductId) GetValue() string { | |||
if x != nil { | |||
return x.Value | |||
} | |||
return "" | |||
} | |||
var File_product_proto protoreflect.FileDescriptor | |||
var file_product_proto_rawDesc = []byte{ | |||
0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, | |||
0x07, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x22, 0x19, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, | |||
0x75, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, | |||
0x02, 0x69, 0x64, 0x22, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, | |||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, | |||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x75, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, | |||
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x0a, 0x61, 0x64, 0x64, 0x50, 0x72, 0x6f, 0x64, | |||
0x75, 0x63, 0x74, 0x12, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x50, 0x72, | |||
0x6f, 0x64, 0x75, 0x63, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, | |||
0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x0a, 0x67, 0x65, 0x74, | |||
0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, | |||
0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x1a, 0x10, 0x2e, 0x70, 0x72, | |||
0x6f, 0x64, 0x75, 0x63, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x56, 0x0a, | |||
0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, | |||
0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, | |||
0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, | |||
0x24, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x73, 0x61, 0x67, 0x61, 0x2f, 0x65, | |||
0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x72, 0x2f, 0x70, 0x72, | |||
0x6f, 0x64, 0x75, 0x63, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, | |||
} | |||
var ( | |||
file_product_proto_rawDescOnce sync.Once | |||
file_product_proto_rawDescData = file_product_proto_rawDesc | |||
) | |||
func file_product_proto_rawDescGZIP() []byte { | |||
file_product_proto_rawDescOnce.Do(func() { | |||
file_product_proto_rawDescData = protoimpl.X.CompressGZIP(file_product_proto_rawDescData) | |||
}) | |||
return file_product_proto_rawDescData | |||
} | |||
var file_product_proto_msgTypes = make([]protoimpl.MessageInfo, 2) | |||
var file_product_proto_goTypes = []interface{}{ | |||
(*Product)(nil), // 0: product.Product | |||
(*ProductId)(nil), // 1: product.ProductId | |||
} | |||
var file_product_proto_depIdxs = []int32{ | |||
0, // 0: product.ProductInfo.addProduct:input_type -> product.Product | |||
1, // 1: product.ProductInfo.getProduct:input_type -> product.ProductId | |||
1, // 2: product.ProductInfo.addProduct:output_type -> product.ProductId | |||
0, // 3: product.ProductInfo.getProduct:output_type -> product.Product | |||
2, // [2:4] is the sub-list for method output_type | |||
0, // [0:2] is the sub-list for method input_type | |||
0, // [0:0] is the sub-list for extension type_name | |||
0, // [0:0] is the sub-list for extension extendee | |||
0, // [0:0] is the sub-list for field type_name | |||
} | |||
func init() { file_product_proto_init() } | |||
func file_product_proto_init() { | |||
if File_product_proto != nil { | |||
return | |||
} | |||
if !protoimpl.UnsafeEnabled { | |||
file_product_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { | |||
switch v := v.(*Product); i { | |||
case 0: | |||
return &v.state | |||
case 1: | |||
return &v.sizeCache | |||
case 2: | |||
return &v.unknownFields | |||
default: | |||
return nil | |||
} | |||
} | |||
file_product_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { | |||
switch v := v.(*ProductId); i { | |||
case 0: | |||
return &v.state | |||
case 1: | |||
return &v.sizeCache | |||
case 2: | |||
return &v.unknownFields | |||
default: | |||
return nil | |||
} | |||
} | |||
} | |||
type x struct{} | |||
out := protoimpl.TypeBuilder{ | |||
File: protoimpl.DescBuilder{ | |||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | |||
RawDescriptor: file_product_proto_rawDesc, | |||
NumEnums: 0, | |||
NumMessages: 2, | |||
NumExtensions: 0, | |||
NumServices: 1, | |||
}, | |||
GoTypes: file_product_proto_goTypes, | |||
DependencyIndexes: file_product_proto_depIdxs, | |||
MessageInfos: file_product_proto_msgTypes, | |||
}.Build() | |||
File_product_proto = out.File | |||
file_product_proto_rawDesc = nil | |||
file_product_proto_goTypes = nil | |||
file_product_proto_depIdxs = nil | |||
} |
@@ -0,0 +1,20 @@ | |||
syntax = "proto3"; | |||
package product; | |||
option go_package = "testdata/saga/engine/invoker/product"; | |||
option java_multiple_files = true; | |||
option java_package = "io.grpc.examples.helloworld"; | |||
option java_outer_classname = "HelloWorldProto"; | |||
service ProductInfo { | |||
rpc addProduct(Product) returns (ProductId); | |||
rpc getProduct(ProductId) returns (Product); | |||
} | |||
message Product { | |||
string id = 1; | |||
} | |||
message ProductId { | |||
string value = 1; | |||
} |
@@ -0,0 +1,150 @@ | |||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. | |||
// versions: | |||
// - protoc-gen-go-grpc v1.3.0 | |||
// - protoc v4.25.3 | |||
// source: product.proto | |||
package product | |||
import ( | |||
context "context" | |||
grpc "google.golang.org/grpc" | |||
codes "google.golang.org/grpc/codes" | |||
status "google.golang.org/grpc/status" | |||
) | |||
// This is a compile-time assertion to ensure that this generated file | |||
// is compatible with the grpc package it is being compiled against. | |||
// Requires gRPC-Go v1.32.0 or later. | |||
const _ = grpc.SupportPackageIsVersion7 | |||
const ( | |||
ProductInfo_AddProduct_FullMethodName = "/product.ProductInfo/addProduct" | |||
ProductInfo_GetProduct_FullMethodName = "/product.ProductInfo/getProduct" | |||
) | |||
// ProductInfoClient is the client API for ProductInfo service. | |||
// | |||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. | |||
type ProductInfoClient interface { | |||
// 添加商品 | |||
AddProduct(ctx context.Context, in *Product, opts ...grpc.CallOption) (*ProductId, error) | |||
// 获取商品 | |||
GetProduct(ctx context.Context, in *ProductId, opts ...grpc.CallOption) (*Product, error) | |||
} | |||
type productInfoClient struct { | |||
cc grpc.ClientConnInterface | |||
} | |||
func NewProductInfoClient(cc grpc.ClientConnInterface) ProductInfoClient { | |||
return &productInfoClient{cc} | |||
} | |||
func (c *productInfoClient) AddProduct(ctx context.Context, in *Product, opts ...grpc.CallOption) (*ProductId, error) { | |||
out := new(ProductId) | |||
err := c.cc.Invoke(ctx, ProductInfo_AddProduct_FullMethodName, in, out, opts...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return out, nil | |||
} | |||
func (c *productInfoClient) GetProduct(ctx context.Context, in *ProductId, opts ...grpc.CallOption) (*Product, error) { | |||
out := new(Product) | |||
err := c.cc.Invoke(ctx, ProductInfo_GetProduct_FullMethodName, in, out, opts...) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return out, nil | |||
} | |||
// ProductInfoServer is the server API for ProductInfo service. | |||
// All implementations must embed UnimplementedProductInfoServer | |||
// for forward compatibility | |||
type ProductInfoServer interface { | |||
// 添加商品 | |||
AddProduct(context.Context, *Product) (*ProductId, error) | |||
// 获取商品 | |||
GetProduct(context.Context, *ProductId) (*Product, error) | |||
mustEmbedUnimplementedProductInfoServer() | |||
} | |||
// UnimplementedProductInfoServer must be embedded to have forward compatible implementations. | |||
type UnimplementedProductInfoServer struct { | |||
} | |||
func (UnimplementedProductInfoServer) AddProduct(context.Context, *Product) (*ProductId, error) { | |||
return nil, status.Errorf(codes.Unimplemented, "method AddProduct not implemented") | |||
} | |||
func (UnimplementedProductInfoServer) GetProduct(context.Context, *ProductId) (*Product, error) { | |||
return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") | |||
} | |||
func (UnimplementedProductInfoServer) mustEmbedUnimplementedProductInfoServer() {} | |||
// UnsafeProductInfoServer may be embedded to opt out of forward compatibility for this service. | |||
// Use of this interface is not recommended, as added methods to ProductInfoServer will | |||
// result in compilation errors. | |||
type UnsafeProductInfoServer interface { | |||
mustEmbedUnimplementedProductInfoServer() | |||
} | |||
func RegisterProductInfoServer(s grpc.ServiceRegistrar, srv ProductInfoServer) { | |||
s.RegisterService(&ProductInfo_ServiceDesc, srv) | |||
} | |||
func _ProductInfo_AddProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||
in := new(Product) | |||
if err := dec(in); err != nil { | |||
return nil, err | |||
} | |||
if interceptor == nil { | |||
return srv.(ProductInfoServer).AddProduct(ctx, in) | |||
} | |||
info := &grpc.UnaryServerInfo{ | |||
Server: srv, | |||
FullMethod: ProductInfo_AddProduct_FullMethodName, | |||
} | |||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||
return srv.(ProductInfoServer).AddProduct(ctx, req.(*Product)) | |||
} | |||
return interceptor(ctx, in, info, handler) | |||
} | |||
func _ProductInfo_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { | |||
in := new(ProductId) | |||
if err := dec(in); err != nil { | |||
return nil, err | |||
} | |||
if interceptor == nil { | |||
return srv.(ProductInfoServer).GetProduct(ctx, in) | |||
} | |||
info := &grpc.UnaryServerInfo{ | |||
Server: srv, | |||
FullMethod: ProductInfo_GetProduct_FullMethodName, | |||
} | |||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { | |||
return srv.(ProductInfoServer).GetProduct(ctx, req.(*ProductId)) | |||
} | |||
return interceptor(ctx, in, info, handler) | |||
} | |||
// ProductInfo_ServiceDesc is the grpc.ServiceDesc for ProductInfo service. | |||
// It's only intended for direct use with grpc.RegisterService, | |||
// and not to be introspected or modified (even as a copy) | |||
var ProductInfo_ServiceDesc = grpc.ServiceDesc{ | |||
ServiceName: "product.ProductInfo", | |||
HandlerType: (*ProductInfoServer)(nil), | |||
Methods: []grpc.MethodDesc{ | |||
{ | |||
MethodName: "addProduct", | |||
Handler: _ProductInfo_AddProduct_Handler, | |||
}, | |||
{ | |||
MethodName: "getProduct", | |||
Handler: _ProductInfo_GetProduct_Handler, | |||
}, | |||
}, | |||
Streams: []grpc.StreamDesc{}, | |||
Metadata: "product.proto", | |||
} |