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 }