github.com/aacfactory/fns@v1.2.86-0.20240310083819-80d667fc0a17/application.go (about)

     1  /*
     2   * Copyright 2023 Wang Min Xiang
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   * 	http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17  
    18  package fns
    19  
    20  import (
    21  	"fmt"
    22  	"github.com/aacfactory/configures"
    23  	"github.com/aacfactory/errors"
    24  	"github.com/aacfactory/fns/barriers"
    25  	"github.com/aacfactory/fns/clusters"
    26  	"github.com/aacfactory/fns/commons/procs"
    27  	"github.com/aacfactory/fns/commons/switchs"
    28  	"github.com/aacfactory/fns/commons/uid"
    29  	"github.com/aacfactory/fns/commons/versions"
    30  	"github.com/aacfactory/fns/configs"
    31  	"github.com/aacfactory/fns/context"
    32  	"github.com/aacfactory/fns/hooks"
    33  	"github.com/aacfactory/fns/logs"
    34  	"github.com/aacfactory/fns/proxies"
    35  	"github.com/aacfactory/fns/runtime"
    36  	"github.com/aacfactory/fns/services"
    37  	"github.com/aacfactory/fns/shareds"
    38  	"github.com/aacfactory/fns/transports"
    39  	"github.com/aacfactory/workers"
    40  	"os"
    41  	"os/signal"
    42  	"strconv"
    43  	"strings"
    44  	"syscall"
    45  	"time"
    46  )
    47  
    48  type Application interface {
    49  	Deploy(service ...services.Service) Application
    50  	Run(ctx context.Context) Application
    51  	Sync()
    52  }
    53  
    54  // +-------------------------------------------------------------------------------------------------------------------+
    55  
    56  func New(options ...Option) (app Application) {
    57  	opt := defaultOptions
    58  	if options != nil {
    59  		for _, option := range options {
    60  			optErr := option(opt)
    61  			if optErr != nil {
    62  				panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed").WithCause(optErr)))
    63  				return
    64  			}
    65  		}
    66  	}
    67  	// app
    68  	appId := strings.TrimSpace(opt.id)
    69  	if appId == "" {
    70  		appId = uid.UID()
    71  	}
    72  	appName := opt.name
    73  	appVersion := opt.version
    74  	// status
    75  	status := &switchs.Switch{}
    76  	// config
    77  	configRetriever, configRetrieverErr := configures.NewRetriever(opt.configRetrieverOption)
    78  	if configRetrieverErr != nil {
    79  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed for invalid config retriever").WithCause(configRetrieverErr)))
    80  		return
    81  	}
    82  	configure, configureErr := configRetriever.Get()
    83  	if configureErr != nil {
    84  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, get config via retriever failed").WithCause(configureErr)))
    85  		return
    86  	}
    87  	config := configs.Config{}
    88  	configErr := configure.As(&config)
    89  	if configErr != nil {
    90  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, decode config failed").WithCause(configErr)))
    91  		return
    92  	}
    93  	// log
    94  	logger, loggerErr := logs.New(config.Log, opt.logWriters)
    95  	if loggerErr != nil {
    96  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new log failed").WithCause(loggerErr)))
    97  		return
    98  	}
    99  
   100  	// proc
   101  	amp := procs.New(config.Runtime.Procs.Min)
   102  	// worker
   103  	workerOptions := make([]workers.Option, 0, 1)
   104  	if workersMax := config.Runtime.Workers.Max; workersMax > 0 {
   105  		workerOptions = append(workerOptions, workers.MaxWorkers(workersMax))
   106  	}
   107  	if workersMaxIdleSeconds := config.Runtime.Workers.MaxIdleSeconds; workersMaxIdleSeconds > 0 {
   108  		workerOptions = append(workerOptions, workers.MaxIdleWorkerDuration(time.Duration(workersMaxIdleSeconds)*time.Second))
   109  	}
   110  	worker := workers.New(workerOptions...)
   111  
   112  	handlers := make([]transports.MuxHandler, 0, 1)
   113  
   114  	var manager services.EndpointsManager
   115  
   116  	local := services.New(appId, appVersion, logger.With("fns", "endpoints"), config.Services, worker)
   117  
   118  	handlers = append(handlers, services.Handler(local))
   119  	handlers = append(handlers, runtime.HealthHandler())
   120  
   121  	// barrier
   122  	var barrier barriers.Barrier
   123  	// shared
   124  	var shared shareds.Shared
   125  	// cluster
   126  	if clusterConfig := config.Cluster; clusterConfig.Name != "" {
   127  		port, portErr := config.Transport.GetPort()
   128  		if portErr != nil {
   129  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed").WithCause(portErr)))
   130  			return
   131  		}
   132  		var clusterHandlers []transports.MuxHandler
   133  		var clusterErr error
   134  		manager, shared, barrier, clusterHandlers, clusterErr = clusters.New(clusters.Options{
   135  			Id:      appId,
   136  			Version: appVersion,
   137  			Port:    port,
   138  			Log:     logger.With("fns", "cluster"),
   139  			Worker:  worker,
   140  			Local:   local,
   141  			Dialer:  opt.transport,
   142  			Config:  clusterConfig,
   143  		})
   144  		if clusterErr != nil {
   145  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed").WithCause(clusterErr)))
   146  			return
   147  		}
   148  		handlers = append(handlers, clusterHandlers...)
   149  	} else {
   150  		var sharedErr error
   151  		shared, sharedErr = shareds.Local(logger.With("shared", "local"), config.Runtime.Shared)
   152  		if sharedErr != nil {
   153  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed").WithCause(sharedErr)))
   154  			return
   155  		}
   156  		barrier = barriers.New()
   157  		manager = local
   158  	}
   159  
   160  	// runtime
   161  	rt := runtime.New(
   162  		appId, appName, appVersion,
   163  		status, logger, worker,
   164  		manager,
   165  		barrier, shared,
   166  	)
   167  
   168  	// builtins
   169  	builtins := make([]services.Service, 0, 1)
   170  
   171  	// transport >>>
   172  	// middlewares
   173  	middlewares := make([]transports.Middleware, 0, 1)
   174  	middlewares = append(middlewares, runtime.Middleware(rt))
   175  	var corsMiddleware transports.Middleware
   176  	for _, middleware := range opt.middlewares {
   177  		builtin, isBuiltin := middleware.(services.Middleware)
   178  		if isBuiltin {
   179  			builtins = append(builtins, builtin.Services()...)
   180  		}
   181  		if middleware.Name() == "cors" {
   182  			corsMiddleware = middleware
   183  			continue
   184  		}
   185  		middlewares = append(middlewares, middleware)
   186  	}
   187  	if corsMiddleware != nil {
   188  		middlewares = append([]transports.Middleware{corsMiddleware}, middlewares...)
   189  	}
   190  	middleware, middlewareErr := transports.WaveMiddlewares(logger, config.Transport, middlewares)
   191  	if middlewareErr != nil {
   192  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new transport middleware failed").WithCause(middlewareErr)))
   193  		return
   194  	}
   195  	// handler
   196  	mux := transports.NewMux()
   197  	handlers = append(handlers, opt.handlers...)
   198  	for _, handler := range handlers {
   199  		handlerConfig, handlerConfigErr := config.Transport.HandlerConfig(handler.Name())
   200  		if handlerConfigErr != nil {
   201  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new transport handler failed").WithCause(handlerConfigErr).WithMeta("handler", handler.Name())))
   202  			return
   203  		}
   204  		handlerErr := handler.Construct(transports.MuxHandlerOptions{
   205  			Log:    logger.With("handler", handler.Name()),
   206  			Config: handlerConfig,
   207  		})
   208  		if handlerErr != nil {
   209  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new transport handler failed").WithCause(handlerErr).WithMeta("handler", handler.Name())))
   210  			return
   211  		}
   212  		mux.Add(handler)
   213  		builtin, isBuiltin := handler.(services.MuxHandler)
   214  		if isBuiltin {
   215  			builtins = append(builtins, builtin.Services()...)
   216  		}
   217  	}
   218  	transport := opt.transport
   219  	transportErr := transport.Construct(transports.Options{
   220  		Log:     logger.With("transport", transport.Name()),
   221  		Config:  config.Transport,
   222  		Handler: middleware.Handler(mux),
   223  	})
   224  	if transportErr != nil {
   225  		panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new transport failed").WithCause(transportErr)))
   226  		return
   227  	}
   228  	// transport <<<
   229  
   230  	// proxy >>>
   231  	var proxy proxies.Proxy
   232  	if proxyOptions := opt.proxyOptions; len(proxyOptions) > 0 {
   233  		cluster, ok := manager.(clusters.ClusterEndpointsManager)
   234  		if !ok {
   235  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new proxy failed").WithCause(fmt.Errorf("application was not in cluster mode"))))
   236  			return
   237  		}
   238  		var proxyErr error
   239  		proxy, proxyErr = proxies.New(proxyOptions...)
   240  		if proxyErr != nil {
   241  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new proxy failed").WithCause(proxyErr)))
   242  			return
   243  		}
   244  		constructErr := proxy.Construct(proxies.ProxyOptions{
   245  			Log:     logger.With("fns", "proxy"),
   246  			Config:  config.Proxy,
   247  			Runtime: rt,
   248  			Manager: cluster,
   249  			Dialer:  transport,
   250  		})
   251  		if constructErr != nil {
   252  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new proxy failed").WithCause(constructErr)))
   253  			return
   254  		}
   255  	}
   256  	// proxy <<<
   257  
   258  	// hooks
   259  	for _, hook := range opt.hooks {
   260  		hookConfig, hookConfigErr := config.Hooks.Get(hook.Name())
   261  		if hookConfigErr != nil {
   262  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new hook failed").WithCause(hookConfigErr)))
   263  			return
   264  		}
   265  		hookErr := hook.Construct(hooks.Options{
   266  			Log:    logger.With("hook", hook.Name()),
   267  			Config: hookConfig,
   268  		})
   269  		if hookErr != nil {
   270  			panic(fmt.Errorf("%+v", errors.Warning("fns: new application failed, new hook failed").WithCause(hookErr)))
   271  			return
   272  		}
   273  	}
   274  
   275  	// signal
   276  	signalCh := make(chan os.Signal, 1)
   277  	signal.Notify(signalCh,
   278  		syscall.SIGINT,
   279  		syscall.SIGKILL,
   280  		syscall.SIGQUIT,
   281  		syscall.SIGABRT,
   282  		syscall.SIGTERM,
   283  	)
   284  	app = &application{
   285  		id:              appId,
   286  		name:            appName,
   287  		version:         appVersion,
   288  		rt:              rt,
   289  		status:          status,
   290  		shared:          shared,
   291  		log:             logger,
   292  		config:          config,
   293  		amp:             amp,
   294  		worker:          worker,
   295  		manager:         manager,
   296  		middlewares:     middleware,
   297  		transport:       transport,
   298  		proxy:           proxy,
   299  		hooks:           opt.hooks,
   300  		shutdownTimeout: opt.shutdownTimeout,
   301  		synced:          false,
   302  		signalCh:        signalCh,
   303  	}
   304  	// deploy
   305  	app.Deploy(builtins...)
   306  	return
   307  }
   308  
   309  // +-------------------------------------------------------------------------------------------------------------------+
   310  
   311  type application struct {
   312  	id              string
   313  	name            string
   314  	version         versions.Version
   315  	rt              *runtime.Runtime
   316  	status          *switchs.Switch
   317  	shared          shareds.Shared
   318  	log             logs.Logger
   319  	config          configs.Config
   320  	amp             *procs.AutoMaxProcs
   321  	worker          workers.Workers
   322  	manager         services.EndpointsManager
   323  	middlewares     transports.Middlewares
   324  	transport       transports.Transport
   325  	proxy           proxies.Proxy
   326  	hooks           []hooks.Hook
   327  	shutdownTimeout time.Duration
   328  	synced          bool
   329  	signalCh        chan os.Signal
   330  }
   331  
   332  func (app *application) Deploy(s ...services.Service) Application {
   333  	for _, service := range s {
   334  		err := app.manager.Add(service)
   335  		if err != nil {
   336  			panic(fmt.Sprintf("%+v", errors.Warning("fns: deploy failed").WithCause(err)))
   337  			return app
   338  		}
   339  	}
   340  	return app
   341  }
   342  
   343  func (app *application) Run(ctx context.Context) Application {
   344  	app.amp.Enable()
   345  	// with runtime
   346  	runtime.With(ctx, app.rt)
   347  	// on
   348  	app.status.On()
   349  	// transport
   350  	trErrs := make(chan error, 1)
   351  	go func(ctx context.Context, transport transports.Transport, errs chan error) {
   352  		lnErr := transport.ListenAndServe()
   353  		if lnErr != nil {
   354  			errs <- lnErr
   355  			close(errs)
   356  		}
   357  	}(ctx, app.transport, trErrs)
   358  	select {
   359  	case trErr := <-trErrs:
   360  		app.amp.Reset()
   361  		panic(fmt.Sprintf("%+v", errors.Warning("fns: application run failed").WithCause(trErr)))
   362  		return app
   363  	case <-time.After(3 * time.Second):
   364  		break
   365  	}
   366  	if app.log.DebugEnabled() {
   367  		app.log.Debug().With("port", strconv.Itoa(app.transport.Port())).Message("fns: transport is serving...")
   368  	}
   369  
   370  	// endpoints
   371  	lnErr := app.manager.Listen(ctx)
   372  	if lnErr != nil {
   373  		app.shutdown()
   374  		panic(fmt.Sprintf("%+v", errors.Warning("fns: application run failed").WithCause(lnErr)))
   375  		return app
   376  	}
   377  	// confirm
   378  	app.status.Confirm()
   379  	// proxy
   380  	if app.proxy != nil {
   381  		prErrs := make(chan error, 1)
   382  		go func(ctx context.Context, proxy proxies.Proxy, errs chan error) {
   383  			proxyErr := proxy.Run(ctx)
   384  			if proxyErr != nil {
   385  				errs <- proxyErr
   386  				close(errs)
   387  			}
   388  		}(ctx, app.proxy, prErrs)
   389  		select {
   390  		case prErr := <-prErrs:
   391  			app.shutdown()
   392  			panic(fmt.Sprintf("%+v", errors.Warning("fns: application run failed").WithCause(prErr)))
   393  			return app
   394  		case <-time.After(3 * time.Second):
   395  			break
   396  		}
   397  		if app.log.DebugEnabled() {
   398  			app.log.Debug().With("port", strconv.Itoa(app.proxy.Port())).Message("fns: proxy is serving...")
   399  		}
   400  	}
   401  	// hooks
   402  	for _, hook := range app.hooks {
   403  		name := hook.Name()
   404  		if name == "" {
   405  			if app.log.DebugEnabled() {
   406  				app.log.Debug().Message("fns: one hook has no name")
   407  			}
   408  			continue
   409  		}
   410  		hookConfig, hookConfigErr := app.config.Hooks.Get(name)
   411  		if hookConfigErr != nil {
   412  			if app.log.DebugEnabled() {
   413  				app.log.Debug().With("hook", name).Cause(hookConfigErr).Message("fns: get hook config failed")
   414  			}
   415  			continue
   416  		}
   417  		hookErr := hook.Construct(hooks.Options{
   418  			Log:    app.log.With("hook", name),
   419  			Config: hookConfig,
   420  		})
   421  		if hookErr != nil {
   422  			if app.log.DebugEnabled() {
   423  				app.log.Debug().With("hook", name).Cause(hookErr).Message("fns: construct hook failed")
   424  			}
   425  			continue
   426  		}
   427  		go hook.Execute(ctx)
   428  		if app.log.DebugEnabled() {
   429  			app.log.Debug().With("hook", hook.Name()).Message("fns: hook is dispatched")
   430  		}
   431  	}
   432  	// log
   433  	if app.log.DebugEnabled() {
   434  		app.log.Debug().Message("fns: application is running...")
   435  	}
   436  	return app
   437  }
   438  
   439  func (app *application) Sync() {
   440  	if app.synced {
   441  		return
   442  	}
   443  	app.synced = true
   444  	<-app.signalCh
   445  	app.shutdown()
   446  	return
   447  }
   448  
   449  func (app *application) shutdown() {
   450  	if off, _ := app.status.IsOff(); off {
   451  		return
   452  	}
   453  	// status
   454  	app.status.Off()
   455  
   456  	defer app.amp.Reset()
   457  	timeout := app.shutdownTimeout
   458  	if timeout < 1 {
   459  		timeout = 10 * time.Minute
   460  	}
   461  	ctx, cancel := context.WithTimeout(context.TODO(), timeout)
   462  
   463  	runtime.With(ctx, app.rt)
   464  
   465  	go func(ctx context.Context, cancel context.CancelFunc, app *application) {
   466  		// hooks
   467  		for _, hook := range app.hooks {
   468  			hook.Shutdown(ctx)
   469  		}
   470  		// endpoints
   471  		app.manager.Shutdown(ctx)
   472  		// transport
   473  		app.middlewares.Close()
   474  		app.transport.Shutdown(ctx)
   475  		// proxy
   476  		if app.proxy != nil {
   477  			app.proxy.Shutdown(ctx)
   478  		}
   479  		// shared
   480  		app.shared.Close()
   481  		// log
   482  		_ = app.log.Shutdown(ctx)
   483  		cancel()
   484  	}(context.Wrap(ctx), cancel, app)
   485  	<-ctx.Done()
   486  	app.status.Confirm()
   487  	// log
   488  	if app.log.DebugEnabled() {
   489  		app.log.Debug().Message("fns: application is stopped!!!")
   490  	}
   491  }