server: add BatchSubscriber
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
@@ -18,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
subSig = "func(context.Context, interface{}) error"
|
||||
subSig = "func(context.Context, interface{}) error"
|
||||
batchSubSig = "func([]context.Context, []interface{}) error"
|
||||
)
|
||||
|
||||
// Precompute the reflect type for error. Can't use error directly
|
||||
@@ -57,26 +58,33 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
|
||||
// ValidateSubscriber func
|
||||
// ValidateSubscriber func signature
|
||||
func ValidateSubscriber(sub Subscriber) error {
|
||||
typ := reflect.TypeOf(sub.Subscriber())
|
||||
var argType reflect.Type
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Func:
|
||||
name := "Func"
|
||||
switch typ.NumIn() {
|
||||
case 2:
|
||||
argType = typ.In(1)
|
||||
if sub.Options().Batch {
|
||||
if argType.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("subscriber %v dont have required signature %s", name, batchSubSig)
|
||||
}
|
||||
if strings.Compare(fmt.Sprintf("%s", argType), "[]interface{}") == 0 {
|
||||
return fmt.Errorf("subscriber %v dont have required signaure %s", name, batchSubSig)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
|
||||
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s or %s", name, typ.NumIn(), subSig, batchSubSig)
|
||||
}
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
|
||||
}
|
||||
if typ.NumOut() != 1 {
|
||||
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
|
||||
name, typ.NumOut(), subSig)
|
||||
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
|
||||
name, typ.NumOut(), subSig, batchSubSig)
|
||||
}
|
||||
if returnType := typ.Out(0); returnType != typeOfError {
|
||||
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
|
||||
@@ -87,13 +95,12 @@ func ValidateSubscriber(sub Subscriber) error {
|
||||
|
||||
for m := 0; m < typ.NumMethod(); m++ {
|
||||
method := typ.Method(m)
|
||||
|
||||
switch method.Type.NumIn() {
|
||||
case 3:
|
||||
argType = method.Type.In(2)
|
||||
default:
|
||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
|
||||
name, method.Name, method.Type.NumIn(), subSig)
|
||||
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
|
||||
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
|
||||
}
|
||||
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
@@ -101,8 +108,8 @@ func ValidateSubscriber(sub Subscriber) error {
|
||||
}
|
||||
if method.Type.NumOut() != 1 {
|
||||
return fmt.Errorf(
|
||||
"subscriber %v.%v has wrong number of outs: %v require signature %s",
|
||||
name, method.Name, method.Type.NumOut(), subSig)
|
||||
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
|
||||
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
|
||||
}
|
||||
if returnType := method.Type.Out(0); returnType != typeOfError {
|
||||
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
|
||||
@@ -183,7 +190,125 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
|
||||
func (n *noopServer) newBatchSubHandler(sb *subscriber, opts Options) broker.BatchHandler {
|
||||
return func(ps broker.Events) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
n.RLock()
|
||||
config := n.opts
|
||||
n.RUnlock()
|
||||
if config.Logger.V(logger.ErrorLevel) {
|
||||
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
|
||||
config.Logger.Error(n.opts.Context, string(debug.Stack()))
|
||||
}
|
||||
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
msgs := make([]Message, 0, len(ps))
|
||||
ctxs := make([]context.Context, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
msg := p.Message()
|
||||
// if we don't have headers, create empty map
|
||||
if msg.Header == nil {
|
||||
msg.Header = metadata.New(2)
|
||||
}
|
||||
|
||||
ct, _ := msg.Header.Get(metadata.HeaderContentType)
|
||||
if len(ct) == 0 {
|
||||
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
|
||||
ct = defaultContentType
|
||||
}
|
||||
hdr := metadata.Copy(msg.Header)
|
||||
topic, _ := msg.Header.Get(metadata.HeaderTopic)
|
||||
ctxs = append(ctxs, metadata.NewIncomingContext(sb.opts.Context, hdr))
|
||||
msgs = append(msgs, &rpcMessage{
|
||||
topic: topic,
|
||||
contentType: ct,
|
||||
header: msg.Header,
|
||||
body: msg.Body,
|
||||
})
|
||||
}
|
||||
results := make(chan error, len(sb.handlers))
|
||||
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
handler := sb.handlers[i]
|
||||
|
||||
var req reflect.Value
|
||||
|
||||
switch handler.reqType.Kind() {
|
||||
case reflect.Ptr:
|
||||
req = reflect.New(handler.reqType.Elem())
|
||||
default:
|
||||
req = reflect.New(handler.reqType.Elem()).Elem()
|
||||
}
|
||||
|
||||
reqType := handler.reqType
|
||||
|
||||
for _, msg := range msgs {
|
||||
cf, err := n.newCodec(msg.ContentType())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rb := reflect.New(req.Type().Elem())
|
||||
if err = cf.ReadBody(bytes.NewReader(msg.Body()), rb.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
msg.(*rpcMessage).codec = cf
|
||||
msg.(*rpcMessage).payload = rb.Interface()
|
||||
}
|
||||
|
||||
fn := func(ctxs []context.Context, ms []Message) error {
|
||||
var vals []reflect.Value
|
||||
if sb.typ.Kind() != reflect.Func {
|
||||
vals = append(vals, sb.rcvr)
|
||||
}
|
||||
if handler.ctxType != nil {
|
||||
vals = append(vals, reflect.ValueOf(ctxs))
|
||||
}
|
||||
payloads := reflect.MakeSlice(reqType, 0, len(ms))
|
||||
for _, m := range ms {
|
||||
payloads = reflect.Append(payloads, reflect.ValueOf(m.Payload()))
|
||||
}
|
||||
vals = append(vals, payloads)
|
||||
|
||||
returnValues := handler.method.Call(vals)
|
||||
if rerr := returnValues[0].Interface(); rerr != nil {
|
||||
return rerr.(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(opts.BatchSubWrappers); i > 0; i-- {
|
||||
fn = opts.BatchSubWrappers[i-1](fn)
|
||||
}
|
||||
|
||||
if n.wg != nil {
|
||||
n.wg.Add(1)
|
||||
}
|
||||
go func() {
|
||||
if n.wg != nil {
|
||||
defer n.wg.Done()
|
||||
}
|
||||
results <- fn(ctxs, msgs)
|
||||
}()
|
||||
}
|
||||
|
||||
var errors []string
|
||||
for i := 0; i < len(sb.handlers); i++ {
|
||||
if rerr := <-results; rerr != nil {
|
||||
errors = append(errors, rerr.Error())
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *noopServer) newSubHandler(sb *subscriber, opts Options) broker.Handler {
|
||||
return func(p broker.Event) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -201,12 +326,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
msg := p.Message()
|
||||
// if we don't have headers, create empty map
|
||||
if msg.Header == nil {
|
||||
msg.Header = make(map[string]string)
|
||||
msg.Header = metadata.New(2)
|
||||
}
|
||||
|
||||
ct := msg.Header["Content-Type"]
|
||||
if len(ct) == 0 {
|
||||
msg.Header["Content-Type"] = defaultContentType
|
||||
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
|
||||
ct = defaultContentType
|
||||
}
|
||||
cf, err := n.newCodec(ct)
|
||||
@@ -214,12 +339,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
return err
|
||||
}
|
||||
|
||||
hdr := make(map[string]string, len(msg.Header))
|
||||
hdr := metadata.New(len(msg.Header))
|
||||
for k, v := range msg.Header {
|
||||
if k == "Content-Type" {
|
||||
continue
|
||||
}
|
||||
hdr[k] = v
|
||||
hdr.Set(k, v)
|
||||
}
|
||||
|
||||
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
|
||||
@@ -294,7 +419,6 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
|
||||
if len(errors) > 0 {
|
||||
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user