2021-01-08 00:58:58 +08:00
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
2019-02-28 23:57:20 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp
import (
2019-06-19 09:06:52 +08:00
"bytes"
2021-06-26 16:23:54 +08:00
"context"
2019-06-19 09:06:52 +08:00
"fmt"
"os"
"runtime"
"strings"
"sync"
"time"
2019-07-29 21:01:19 +08:00
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/encoding/gjson"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/intlog"
2024-09-19 14:10:16 +08:00
"github.com/gogf/gf/v2/os/gfile"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/os/gtimer"
2021-11-13 23:23:55 +08:00
"github.com/gogf/gf/v2/text/gstr"
2021-10-11 21:41:56 +08:00
"github.com/gogf/gf/v2/util/gconv"
2019-02-28 23:57:20 +08:00
)
const (
2020-05-01 03:31:04 +08:00
// Allow executing management command after server starts after this interval in milliseconds.
2020-12-15 20:16:17 +08:00
adminActionIntervalLimit = 2000
adminActionNone = 0
adminActionRestarting = 1
adminActionShuttingDown = 2
adminActionReloadEnvKey = "GF_SERVER_RELOAD"
adminActionRestartEnvKey = "GF_SERVER_RESTART"
adminGProcCommGroup = "GF_GPROC_HTTP_SERVER"
2019-02-28 23:57:20 +08:00
)
2021-09-27 21:27:24 +08:00
var (
// serverActionLocker is the locker for server administration operations.
serverActionLocker sync . Mutex
2019-02-28 23:57:20 +08:00
2021-09-27 21:27:24 +08:00
// serverActionLastTime is timestamp in milliseconds of last administration operation.
serverActionLastTime = gtype . NewInt64 ( gtime . TimestampMilli ( ) )
2019-02-28 23:57:20 +08:00
2021-09-27 21:27:24 +08:00
// serverProcessStatus is the server status for operation of current process.
serverProcessStatus = gtype . NewInt ( )
)
2019-02-28 23:57:20 +08:00
2023-09-11 10:15:08 +08:00
// RestartAllServer restarts all the servers of the process gracefully.
2021-10-21 18:22:47 +08:00
// The optional parameter `newExeFilePath` specifies the new binary file for creating process.
2023-09-11 10:15:08 +08:00
func RestartAllServer ( ctx context . Context , newExeFilePath string ) error {
2020-07-25 13:50:04 +08:00
if ! gracefulEnabled {
2024-09-19 14:10:16 +08:00
return gerror . NewCode (
gcode . CodeInvalidOperation ,
"graceful reload feature is disabled" ,
)
2020-07-25 13:50:04 +08:00
}
2019-06-19 09:06:52 +08:00
serverActionLocker . Lock ( )
defer serverActionLocker . Unlock ( )
if err := checkProcessStatus ( ) ; err != nil {
return err
}
2020-05-01 03:31:04 +08:00
if err := checkActionFrequency ( ) ; err != nil {
2019-06-19 09:06:52 +08:00
return err
}
2023-09-11 10:15:08 +08:00
return restartWebServers ( ctx , nil , newExeFilePath )
2019-02-28 23:57:20 +08:00
}
2023-09-11 10:15:08 +08:00
// ShutdownAllServer shuts down all servers of current process gracefully.
2021-09-27 21:27:24 +08:00
func ShutdownAllServer ( ctx context . Context ) error {
2019-06-19 09:06:52 +08:00
serverActionLocker . Lock ( )
defer serverActionLocker . Unlock ( )
if err := checkProcessStatus ( ) ; err != nil {
return err
}
2020-05-01 03:31:04 +08:00
if err := checkActionFrequency ( ) ; err != nil {
2019-06-19 09:06:52 +08:00
return err
}
2023-09-11 10:15:08 +08:00
shutdownWebServersGracefully ( ctx , nil )
2019-06-19 09:06:52 +08:00
return nil
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// checkProcessStatus checks the server status of current process.
2019-02-28 23:57:20 +08:00
func checkProcessStatus ( ) error {
2019-06-19 09:06:52 +08:00
status := serverProcessStatus . Val ( )
if status > 0 {
switch status {
2020-12-15 20:16:17 +08:00
case adminActionRestarting :
2021-08-24 21:18:59 +08:00
return gerror . NewCode ( gcode . CodeInvalidOperation , "server is restarting" )
2021-07-20 23:02:02 +08:00
2020-12-15 20:16:17 +08:00
case adminActionShuttingDown :
2021-08-24 21:18:59 +08:00
return gerror . NewCode ( gcode . CodeInvalidOperation , "server is shutting down" )
2019-06-19 09:06:52 +08:00
}
}
return nil
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// checkActionFrequency checks the operation frequency.
// It returns error if it is too frequency.
func checkActionFrequency ( ) error {
2020-01-20 14:14:11 +08:00
interval := gtime . TimestampMilli ( ) - serverActionLastTime . Val ( )
2020-12-15 20:16:17 +08:00
if interval < adminActionIntervalLimit {
2021-07-20 23:02:02 +08:00
return gerror . NewCodef (
2021-08-24 21:18:59 +08:00
gcode . CodeInvalidOperation ,
2021-07-20 23:02:02 +08:00
"too frequent action, please retry in %d ms" ,
adminActionIntervalLimit - interval ,
)
2019-06-19 09:06:52 +08:00
}
2020-01-20 14:14:11 +08:00
serverActionLastTime . Set ( gtime . TimestampMilli ( ) )
2019-06-19 09:06:52 +08:00
return nil
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// forkReloadProcess creates a new child process and copies the fd to child process.
2021-09-27 21:27:24 +08:00
func forkReloadProcess ( ctx context . Context , newExeFilePath ... string ) error {
var (
2026-04-22 20:44:57 +08:00
binaryPath = gfile . SelfPath ( )
2021-09-27 21:27:24 +08:00
)
2023-12-14 21:51:28 +08:00
if len ( newExeFilePath ) > 0 && newExeFilePath [ 0 ] != "" {
2024-09-19 14:10:16 +08:00
binaryPath = newExeFilePath [ 0 ]
}
2026-04-22 20:44:57 +08:00
if binaryPath == "" {
return gerror . NewCodef (
gcode . CodeInvalidOperation ,
"cannot determine current executable path: gfile.SelfPath() returned empty and no executable override was provided (goos=%s, goarch=%s, overrideProvided=%t)" ,
runtime . GOOS ,
runtime . GOARCH ,
len ( newExeFilePath ) > 0 && newExeFilePath [ 0 ] != "" ,
)
}
2024-09-19 14:10:16 +08:00
if ! gfile . Exists ( binaryPath ) {
return gerror . Newf ( ` binary file path "%s" does not exist ` , binaryPath )
2019-06-19 09:06:52 +08:00
}
2020-05-01 03:31:04 +08:00
var (
2026-04-22 20:44:57 +08:00
p = gproc . NewProcess ( binaryPath , getCurrentProcessArgs ( ) , os . Environ ( ) )
2020-05-01 03:31:04 +08:00
sfm = getServerFdMap ( )
)
2019-06-19 09:06:52 +08:00
for name , m := range sfm {
for fdk , fdv := range m {
if len ( fdv ) > 0 {
s := ""
2020-06-28 20:52:33 +08:00
for _ , item := range gstr . SplitAndTrim ( fdv , "," ) {
2019-06-19 09:06:52 +08:00
array := strings . Split ( item , "#" )
fd := uintptr ( gconv . Uint ( array [ 1 ] ) )
if fd > 0 {
s += fmt . Sprintf ( "%s#%d," , array [ 0 ] , 3 + len ( p . ExtraFiles ) )
p . ExtraFiles = append ( p . ExtraFiles , os . NewFile ( fd , "" ) )
} else {
s += fmt . Sprintf ( "%s#%d," , array [ 0 ] , 0 )
}
}
sfm [ name ] [ fdk ] = strings . TrimRight ( s , "," )
}
}
}
buffer , _ := gjson . Encode ( sfm )
2020-12-15 20:16:17 +08:00
p . Env = append ( p . Env , adminActionReloadEnvKey + "=" + string ( buffer ) )
2022-06-21 21:46:12 +08:00
if _ , err := p . Start ( ctx ) ; err != nil {
2024-09-19 14:10:16 +08:00
intlog . Errorf (
2021-09-27 21:27:24 +08:00
ctx ,
2024-09-19 14:10:16 +08:00
"%d: fork process failed, error: %s, %s" ,
2021-09-27 21:27:24 +08:00
gproc . Pid ( ) , err . Error ( ) , string ( buffer ) ,
)
2019-06-19 09:06:52 +08:00
return err
}
return nil
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// forkRestartProcess creates a new server process.
2023-12-14 21:51:28 +08:00
func forkRestartProcess ( ctx context . Context , newExeFilePath ... string ) error {
2021-09-27 21:27:24 +08:00
var (
2026-04-22 20:44:57 +08:00
path = gfile . SelfPath ( )
2021-09-27 21:27:24 +08:00
)
2023-12-14 21:51:28 +08:00
if len ( newExeFilePath ) > 0 && newExeFilePath [ 0 ] != "" {
path = newExeFilePath [ 0 ]
2019-06-19 09:06:52 +08:00
}
2026-04-22 20:44:57 +08:00
if path == "" {
return gerror . NewCode ( gcode . CodeInvalidOperation , "cannot determine current executable path" )
}
2021-09-27 21:27:24 +08:00
if err := os . Unsetenv ( adminActionReloadEnvKey ) ; err != nil {
2022-01-28 14:51:49 +08:00
intlog . Errorf ( ctx , ` %+v ` , err )
2021-09-27 21:27:24 +08:00
}
2019-06-19 09:06:52 +08:00
env := os . Environ ( )
2020-12-15 20:16:17 +08:00
env = append ( env , adminActionRestartEnvKey + "=1" )
2026-04-22 20:44:57 +08:00
p := gproc . NewProcess ( path , getCurrentProcessArgs ( ) , env )
2022-06-21 21:46:12 +08:00
if _ , err := p . Start ( ctx ) ; err != nil {
2021-09-27 21:27:24 +08:00
glog . Errorf (
ctx ,
` %d: fork process failed, error:%s, are you running using "go run"? ` ,
gproc . Pid ( ) , err . Error ( ) ,
)
2019-06-19 09:06:52 +08:00
return err
}
return nil
2019-02-28 23:57:20 +08:00
}
2026-04-22 20:44:57 +08:00
func getCurrentProcessArgs ( ) [ ] string {
if len ( os . Args ) > 1 {
return os . Args [ 1 : ]
}
return nil
}
2020-05-01 03:31:04 +08:00
// getServerFdMap returns all the servers name to file descriptor mapping as map.
2019-02-28 23:57:20 +08:00
func getServerFdMap ( ) map [ string ] listenerFdMap {
2019-06-19 09:06:52 +08:00
sfm := make ( map [ string ] listenerFdMap )
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
serverMapping . RLockFunc ( func ( m map [ string ] * Server ) {
2019-06-19 09:06:52 +08:00
for k , v := range m {
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
sfm [ k ] = v . getListenerFdMap ( )
2019-06-19 09:06:52 +08:00
}
} )
return sfm
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// bufferToServerFdMap converts binary content to fd map.
2019-02-28 23:57:20 +08:00
func bufferToServerFdMap ( buffer [ ] byte ) map [ string ] listenerFdMap {
2019-06-19 09:06:52 +08:00
sfm := make ( map [ string ] listenerFdMap )
if len ( buffer ) > 0 {
j , _ := gjson . LoadContent ( buffer )
2022-03-19 17:58:21 +08:00
for k := range j . Var ( ) . Map ( ) {
2019-06-19 09:06:52 +08:00
m := make ( map [ string ] string )
2023-09-11 10:15:08 +08:00
for mapKey , mapValue := range j . Get ( k ) . MapStrStr ( ) {
m [ mapKey ] = mapValue
2019-06-19 09:06:52 +08:00
}
sfm [ k ] = m
}
}
return sfm
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// restartWebServers restarts all servers.
2023-09-11 10:15:08 +08:00
func restartWebServers ( ctx context . Context , signal os . Signal , newExeFilePath string ) error {
2020-12-15 20:16:17 +08:00
serverProcessStatus . Set ( adminActionRestarting )
2019-06-19 09:06:52 +08:00
if runtime . GOOS == "windows" {
2023-09-11 10:15:08 +08:00
if signal != nil {
2020-05-01 03:31:04 +08:00
// Controlled by signal.
2021-09-27 21:27:24 +08:00
forceCloseWebServers ( ctx )
2023-09-11 10:15:08 +08:00
if err := forkRestartProcess ( ctx , newExeFilePath ) ; err != nil {
2022-01-28 14:51:49 +08:00
intlog . Errorf ( ctx , ` %+v ` , err )
2021-09-27 21:27:24 +08:00
}
2023-09-11 10:15:08 +08:00
return nil
2019-06-19 09:06:52 +08:00
}
2023-09-11 10:15:08 +08:00
// Controlled by web page.
// It should ensure the response wrote to client and then close all servers gracefully.
gtimer . SetTimeout ( ctx , time . Second , func ( ctx context . Context ) {
forceCloseWebServers ( ctx )
if err := forkRestartProcess ( ctx , newExeFilePath ) ; err != nil {
intlog . Errorf ( ctx , ` %+v ` , err )
}
} )
return nil
}
if err := forkReloadProcess ( ctx , newExeFilePath ) ; err != nil {
glog . Printf ( ctx , "%d: server restarts failed" , gproc . Pid ( ) )
serverProcessStatus . Set ( adminActionNone )
return err
2019-06-19 09:06:52 +08:00
} else {
2023-09-11 10:15:08 +08:00
if signal != nil {
glog . Printf ( ctx , "%d: server restarting by signal: %s" , gproc . Pid ( ) , signal )
2019-06-19 09:06:52 +08:00
} else {
2023-09-11 10:15:08 +08:00
glog . Printf ( ctx , "%d: server restarting by web admin" , gproc . Pid ( ) )
2019-06-19 09:06:52 +08:00
}
}
2019-02-28 23:57:20 +08:00
2023-09-11 10:15:08 +08:00
return nil
2019-02-28 23:57:20 +08:00
}
2021-01-08 00:58:58 +08:00
// shutdownWebServersGracefully gracefully shuts down all servers.
2023-09-11 10:15:08 +08:00
func shutdownWebServersGracefully ( ctx context . Context , signal os . Signal ) {
serverProcessStatus . Set ( adminActionShuttingDown )
if signal != nil {
glog . Printf (
ctx ,
"%d: server gracefully shutting down by signal: %s" ,
gproc . Pid ( ) , signal . String ( ) ,
)
2021-01-08 00:58:58 +08:00
} else {
2024-09-19 14:10:16 +08:00
glog . Printf ( ctx , "pid[%d]: server gracefully shutting down by api" , gproc . Pid ( ) )
2021-01-08 00:58:58 +08:00
}
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
serverMapping . RLockFunc ( func ( m map [ string ] * Server ) {
2019-06-19 09:06:52 +08:00
for _ , v := range m {
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
v . doServiceDeregister ( )
for _ , s := range v . servers {
2024-12-10 09:52:48 +08:00
s . Shutdown ( ctx )
2019-06-19 09:06:52 +08:00
}
}
} )
2019-02-28 23:57:20 +08:00
}
2020-05-01 03:31:04 +08:00
// forceCloseWebServers forced shuts down all servers.
2021-09-27 21:27:24 +08:00
func forceCloseWebServers ( ctx context . Context ) {
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
serverMapping . RLockFunc ( func ( m map [ string ] * Server ) {
2019-06-19 09:06:52 +08:00
for _ , v := range m {
feat(instance): migrate instance containers to type-safe generics (#4617)
### 变更说明
本次重构将项目中用于**实例管理的容器**从 `StrAnyMap`/`IntAnyMap` 迁移到类型安全的泛型实现
`KVMapWithChecker`,同时将相关的 `glist.List` 和 `gqueue.Queue`
替换为对应的泛型版本,以提高实例管理的类型安全性。并且减少原先代码中的大量类型断言,提高性能。
### 前因
目前`goframe`中大量使用了包含`any`的容器,然后通过断言去转换类型,麻烦且影响性能,尤其是对`gdb/gredis/glog`等需要高频获取`instance`实例的组件影响较大。最近几个版本中gf完成了数据结构容器的泛型化改造,以及我最近解决了其中几个泛型容器对于`typed
nil`过滤的问题,所以可以逐步迁移这些实例容器到泛型容器,减少断言优化性能
### 主要改进
#### 1. 实例容器泛型化
以下模块的实例管理容器已迁移到泛型实现:
**核心实例管理**:
- `database/gdb`: 数据库实例容器 → `KVMap[string, DB]`
- `database/gredis`: Redis 实例容器 → `KVMap[string, *Redis]`
- `database/gredis`: Redis 配置容器 → `KVMap[string, *Config]`
- `os/gcfg`: 配置实例容器 → `KVMap[string, *Config]`
- `os/glog`: 日志实例容器 → `KVMap[string, *Logger]`
- `os/gview`: 视图实例容器 → `KVMap[string, *View]`
- `i18n/gi18n`: 国际化实例容器 → `KVMap[string, *Manager]`
**网络服务实例**:
- `net/ghttp`: HTTP 服务器容器 → `KVMap[string, *Server]`
- `net/gtcp`: TCP 服务器容器 → `KVMap[any, *Server]`
- `net/gudp`: UDP 服务器容器 → `KVMap[string, *Server]`
**其他实例容器**:
- `os/gres`: 资源实例容器 → `KVMap[string, *Resource]`
- `os/gfpool`: 文件池容器 → `KVMap[string, *Pool]`
- `os/gspath`: 路径搜索容器 → `KVMap[string, *SPath]`
- `net/gtcp`: 连接池容器 → `KVMap[string, *gpool.Pool]`
#### 2. 相关数据结构泛型化
- `os/gfsnotify`: 回调列表 → `TList[*Callback]`,事件队列 → `TQueue[*Event]`
- `os/grpool`: 任务队列 → `TList[*localPoolItem]`
- `os/gcache`: 事件队列 → `TList[*adapterMemoryEvent]`
- `net/ghttp`: 解析项列表 → `TList[*HandlerItemParsed]`
- `os/gproc`: 消息队列 → `TQueue[*MsgRequest]`
- `os/gmlock`: 锁映射 → `KVMap[string, *sync.RWMutex]`
### 技术实现
1. **引入检查器函数**: 为每个实例容器添加 `checker` 函数用于空值检测
2. **消除类型断言**: 实例获取时无需 `v.(*Type)` 转换
3. **明确函数签名**: `GetOrSetFuncLock` 的回调从 `func() any` 改为 `func() T`
### 使用示例
#### 实例容器的变更
**变更前**:
```go
// 旧的实例管理方式
var instances = gmap.NewStrAnyMap(true)
func Instance(name string) *Logger {
v := instances.GetOrSetFuncLock(name, func() any {
return New()
})
return v.(*Logger) // 需要类型断言
}
```
**变更后**:
```go
// 新的泛型实例容器
var (
checker = func(v *Logger) bool { return v == nil }
instances = gmap.NewKVMapWithChecker[string, *Logger](checker, true)
)
func Instance(name string) *Logger {
return instances.GetOrSetFuncLock(name, New) // 直接返回,无需断言
}
```
#### 队列容器的变更
**变更前**:
```go
// 旧的队列方式
events := gqueue.New()
events.Push(&Event{Path: "/tmp/file"})
if v := events.Pop(); v != nil {
event := v.(*Event) // 需要类型断言
handleEvent(event)
}
```
**变更后**:
```go
// 新的泛型队列
events := gqueue.NewTQueue[*Event]()
events.Push(&Event{Path: "/tmp/file"})
if event := events.Pop(); event != nil {
handleEvent(event) // event 已是 *Event 类型
}
```
### 收益
- ✅ **编译时类型安全**: 实例容器的类型错误在编译期捕获
- ✅ **消除运行时断言**: 避免类型断言带来的 panic 风险
- ✅ **提升代码可读性**: 实例管理逻辑更清晰
- ✅ **改善开发体验**: IDE 类型提示和代码补全更准确
### 性能权衡
**编译时**:
- 泛型实例化会增加编译时间和二进制体积
- 预估编译时间增加 5-15%,二进制体积增加约 1-2MB
**运行时**:
- 减少类型断言的反射开销
- 提升实例获取等热点路径的性能
2026-01-16 15:23:13 +08:00
for _ , s := range v . servers {
2024-12-10 09:52:48 +08:00
s . Close ( ctx )
2019-06-19 09:06:52 +08:00
}
}
} )
2019-02-28 23:57:20 +08:00
}
2020-07-12 09:56:07 +08:00
// handleProcessMessage receives and handles the message from processes,
// which are commonly used for graceful reloading feature.
2019-02-28 23:57:20 +08:00
func handleProcessMessage ( ) {
2021-09-27 21:27:24 +08:00
var (
ctx = context . TODO ( )
)
2019-06-19 09:06:52 +08:00
for {
2020-12-15 20:16:17 +08:00
if msg := gproc . Receive ( adminGProcCommGroup ) ; msg != nil {
2019-06-19 09:06:52 +08:00
if bytes . EqualFold ( msg . Data , [ ] byte ( "exit" ) ) {
2021-09-27 21:27:24 +08:00
intlog . Printf ( ctx , "%d: process message: exit" , gproc . Pid ( ) )
2023-09-11 10:15:08 +08:00
shutdownWebServersGracefully ( ctx , nil )
2022-10-08 21:45:21 +08:00
allShutdownChan <- struct { } { }
2021-09-27 21:27:24 +08:00
intlog . Printf ( ctx , "%d: process message: exit done" , gproc . Pid ( ) )
2019-06-19 09:06:52 +08:00
return
}
}
}
}