pkg/chain/invocation.go (80 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package chain import ( "context" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/util" ) type InvocationOption func(op InvocationOp) InvocationOp type InvocationOp struct { Func CallbackFunc Async bool } // WithFunc append a func to the begin of invocation callback list func WithFunc(f func(r Result)) InvocationOption { return func(op InvocationOp) InvocationOp { op.Func = f; return op } } // WithAsyncFunc called concurrently after all WithFunc finish func WithAsyncFunc(f func(r Result)) InvocationOption { return func(op InvocationOp) InvocationOp { op.Func = f; op.Async = true; return op } } type Invocation struct { Callback context *util.StringContext chain Chain } func (i *Invocation) Init(ctx context.Context, ch Chain) { i.context = util.NewStringContext(ctx) i.chain = ch } func (i *Invocation) Context() context.Context { return i.context } func (i *Invocation) WithContext(key util.CtxKey, val interface{}) *Invocation { i.context.SetKV(key, val) return i } // Next is the method to go next step in handler chain // WithFunc and WithAsyncFunc options can add customize callbacks in chain // and the callbacks seq like below: // when i.Next(WithFunc(CB1)).Next(WithAsyncFunc(CB2)).Next(WithFunc(CB3)).Invoke(CB0) // then i.Success/Fail() -> CB3 -> CB1 -> CB0(invoke) goroutine 0 // // \-> CB2(async) goroutine 1 func (i *Invocation) Next(opts ...InvocationOption) { var op InvocationOp for _, opt := range opts { op = opt(op) } i.setCallback(op.Func, op.Async) i.chain.Next(i) } func (i *Invocation) setCallback(f CallbackFunc, async bool) { if f == nil { return } if i.Func == nil { i.Func = f i.Async = async return } cb := i.Func i.Func = func(r Result) { callback(cb, f, async, r) } } func callback(prev, next CallbackFunc, async bool, r Result) { // we make sure the all sync funcs called before the async funcs if async { prev(r) } c := Callback{Func: next, Async: async} c.Invoke(r) if !async { prev(r) } } func (i *Invocation) Invoke(last CallbackFunc) { defer func() { itf := recover() if itf == nil { return } log.Panic(itf) // this recover only catch the exceptions raised in sync invocations. // The async invocations will be catch by gopool pkg then it never // change the callback results. // i.Fail(discovery.NewError(discovery.ErrInternal, fmt.Sprintf("%v", itf))) }() i.Func = last i.chain.Next(i) } func NewInvocation(ctx context.Context, ch Chain) (inv Invocation) { inv.Init(ctx, ch) return inv }