From 7d91986931e282aeef7c46c48b00e079220f4c6e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 6 May 2018 22:35:08 +0800 Subject: [PATCH 01/31] =?UTF-8?q?=E6=94=B9=E8=BF=9Bghttp=E5=8C=85=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=91=BD=E5=90=8D=EF=BC=8C=E5=A2=9E=E5=8A=A0gbase64?= =?UTF-8?q?=E7=A4=BA=E4=BE=8B=EF=BC=8Cghttp.Server=E7=83=AD=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ent_request.go => ghttp_client_request.go} | 0 ...t_response.go => ghttp_client_response.go} | 0 ...http_controller.go => ghttp_controller.go} | 0 g/net/ghttp/{http_func.go => ghttp_func.go} | 0 .../{http_request.go => ghttp_request.go} | 0 .../{http_response.go => ghttp_response.go} | 0 .../ghttp/{http_server.go => ghttp_server.go} | 48 +++-- ...tp_server_auto.go => ghttp_server_auto.go} | 0 ...erver_config.go => ghttp_server_config.go} | 0 ...erver_cookie.go => ghttp_server_cookie.go} | 0 ...erver_domain.go => ghttp_server_domain.go} | 0 g/net/ghttp/ghttp_server_graceful.go | 189 ++++++++++++++++++ ...ver_handler.go => ghttp_server_handler.go} | 0 ..._server_hooks.go => ghttp_server_hooks.go} | 0 ...http_server_log.go => ghttp_server_log.go} | 0 ...ver_options.go => ghttp_server_options.go} | 0 ..._server_pprof.go => ghttp_server_pprof.go} | 0 ...erver_router.go => ghttp_server_router.go} | 0 ...ver_service.go => ghttp_server_service.go} | 0 ...ver_session.go => ghttp_server_session.go} | 0 ...erver_status.go => ghttp_server_status.go} | 0 g/os/gfile/gfile.go | 5 + geg/encoding/gbase64.go | 18 ++ geg/other/graceful.go | 19 ++ geg/other/test.go | 14 +- 25 files changed, 276 insertions(+), 17 deletions(-) rename g/net/ghttp/{http_client_request.go => ghttp_client_request.go} (100%) rename g/net/ghttp/{http_client_response.go => ghttp_client_response.go} (100%) rename g/net/ghttp/{http_controller.go => ghttp_controller.go} (100%) rename g/net/ghttp/{http_func.go => ghttp_func.go} (100%) rename g/net/ghttp/{http_request.go => ghttp_request.go} (100%) rename g/net/ghttp/{http_response.go => ghttp_response.go} (100%) rename g/net/ghttp/{http_server.go => ghttp_server.go} (88%) rename g/net/ghttp/{http_server_auto.go => ghttp_server_auto.go} (100%) rename g/net/ghttp/{http_server_config.go => ghttp_server_config.go} (100%) rename g/net/ghttp/{http_server_cookie.go => ghttp_server_cookie.go} (100%) rename g/net/ghttp/{http_server_domain.go => ghttp_server_domain.go} (100%) create mode 100644 g/net/ghttp/ghttp_server_graceful.go rename g/net/ghttp/{http_server_handler.go => ghttp_server_handler.go} (100%) rename g/net/ghttp/{http_server_hooks.go => ghttp_server_hooks.go} (100%) rename g/net/ghttp/{http_server_log.go => ghttp_server_log.go} (100%) rename g/net/ghttp/{http_server_options.go => ghttp_server_options.go} (100%) rename g/net/ghttp/{http_server_pprof.go => ghttp_server_pprof.go} (100%) rename g/net/ghttp/{http_server_router.go => ghttp_server_router.go} (100%) rename g/net/ghttp/{http_server_service.go => ghttp_server_service.go} (100%) rename g/net/ghttp/{http_server_session.go => ghttp_server_session.go} (100%) rename g/net/ghttp/{http_server_status.go => ghttp_server_status.go} (100%) create mode 100644 geg/encoding/gbase64.go create mode 100644 geg/other/graceful.go diff --git a/g/net/ghttp/http_client_request.go b/g/net/ghttp/ghttp_client_request.go similarity index 100% rename from g/net/ghttp/http_client_request.go rename to g/net/ghttp/ghttp_client_request.go diff --git a/g/net/ghttp/http_client_response.go b/g/net/ghttp/ghttp_client_response.go similarity index 100% rename from g/net/ghttp/http_client_response.go rename to g/net/ghttp/ghttp_client_response.go diff --git a/g/net/ghttp/http_controller.go b/g/net/ghttp/ghttp_controller.go similarity index 100% rename from g/net/ghttp/http_controller.go rename to g/net/ghttp/ghttp_controller.go diff --git a/g/net/ghttp/http_func.go b/g/net/ghttp/ghttp_func.go similarity index 100% rename from g/net/ghttp/http_func.go rename to g/net/ghttp/ghttp_func.go diff --git a/g/net/ghttp/http_request.go b/g/net/ghttp/ghttp_request.go similarity index 100% rename from g/net/ghttp/http_request.go rename to g/net/ghttp/ghttp_request.go diff --git a/g/net/ghttp/http_response.go b/g/net/ghttp/ghttp_response.go similarity index 100% rename from g/net/ghttp/http_response.go rename to g/net/ghttp/ghttp_response.go diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/ghttp_server.go similarity index 88% rename from g/net/ghttp/http_server.go rename to g/net/ghttp/ghttp_server.go index 1315bb00b..d8e6fcb8e 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -7,6 +7,7 @@ package ghttp import ( + "os" "sync" "errors" "strings" @@ -31,36 +32,45 @@ const ( gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 ) -// http server结构体 +// ghttp.Server结构体 type Server struct { - hmmu sync.RWMutex // handler互斥锁 - hhmu sync.RWMutex // hooks互斥锁 - hsmu sync.RWMutex // status handler互斥锁 - hmcmu sync.RWMutex // handlerCache互斥锁 - hhcmu sync.RWMutex // hooksCache互斥锁 + // 基本属性变量 name string // 服务名称,方便识别 config ServerConfig // 配置对象 status int8 // 当前服务器状态(0:未启动,1:运行中) + servers []*http.Server // 底层http.Server列表 methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充) + servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID + closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) + signalQueue chan os.Signal // 终端命令行监听队列 + // 服务注册相关 + hmmu sync.RWMutex // handler互斥锁 + hmcmu sync.RWMutex // handlerCache互斥锁 handlerMap HandlerMap // 所有注册的回调函数(静态匹配) - statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) handlerTree map[string]interface{} // 所有注册的回调函数(动态匹配,树型+链表优先级匹配) - hooksTree map[string]interface{} // 所有注册的事件回调函数(动态匹配,树型+链表优先级匹配) handlerCache *gcache.Cache // 服务注册路由内存缓存 + // 事件回调注册 + hhmu sync.RWMutex // hooks互斥锁 + hhcmu sync.RWMutex // hooksCache互斥锁 + hooksTree map[string]interface{} // 所有注册的事件回调函数(动态匹配,树型+链表优先级匹配) hooksCache *gcache.Cache // 回调事件注册路由内存缓存 - servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况) + // 自定义状态码回调 + hsmu sync.RWMutex // status handler互斥锁 + statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法) + // COOKIE cookieMaxAge *gtype.Int // Cookie有效期 + cookies *gmap.IntInterfaceMap // 当前服务器正在服务(请求正在执行)的Cookie(每个请求一个Cookie对象) + // SESSION sessionMaxAge *gtype.Int // Session有效期 sessionIdName *gtype.String // SessionId名称 - cookies *gmap.IntInterfaceMap // 当前服务器正在服务(请求正在执行)的Cookie(每个请求一个Cookie对象) sessions *gcache.Cache // Session内存缓存 - closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) + // 日志相关属性 logPath *gtype.String // 存放日志的目录路径 + logHandler *gtype.Interface // 自定义日志处理回调方法 errorLogEnabled *gtype.Bool // 是否开启error log accessLogEnabled *gtype.Bool // 是否开启access log accessLogger *glog.Logger // access log日志对象 errorLogger *glog.Logger // error log日志对象 - logHandler *gtype.Interface // 自定义的日志处理回调方法 } // 域名、URI与回调函数的绑定记录表 @@ -100,6 +110,7 @@ func GetServer(name...interface{}) (*Server) { } s := &Server { name : sname, + servers : make([]*http.Server, 0), methodsMap : make(map[string]bool), handlerMap : make(HandlerMap), statusHandlerMap : make(map[string]HandlerFunc), @@ -114,6 +125,7 @@ func GetServer(name...interface{}) (*Server) { sessionIdName : gtype.NewString(gDEFAULT_SESSION_ID_NAME), servedCount : gtype.NewInt(), closeQueue : gqueue.New(), + signalQueue : make(chan os.Signal), logPath : gtype.NewString(), accessLogEnabled : gtype.NewBool(), errorLogEnabled : gtype.NewBool(true), @@ -145,7 +157,7 @@ func (s *Server) Run() error { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) } - // 开启异步处理队列处理循环 + // 开启异步关闭队列处理循环 s.startCloseQueueLoop() // 开始执行底层Web Server创建,端口监听 @@ -163,9 +175,12 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if err := s.newServer(addr).ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { + server := s.newGracefulServer(addr) + if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { glog.Error(err) wg.Done() + } else { + //s.servers = append(s.servers, server) } }(v) } @@ -178,9 +193,12 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if err := s.newServer(addr).ListenAndServe(); err != nil { + server := s.newGracefulServer(addr) + if err := server.ListenAndServe(); err != nil { glog.Error(err) wg.Done() + } else { + //s.servers = append(s.servers, server) } }(v) } diff --git a/g/net/ghttp/http_server_auto.go b/g/net/ghttp/ghttp_server_auto.go similarity index 100% rename from g/net/ghttp/http_server_auto.go rename to g/net/ghttp/ghttp_server_auto.go diff --git a/g/net/ghttp/http_server_config.go b/g/net/ghttp/ghttp_server_config.go similarity index 100% rename from g/net/ghttp/http_server_config.go rename to g/net/ghttp/ghttp_server_config.go diff --git a/g/net/ghttp/http_server_cookie.go b/g/net/ghttp/ghttp_server_cookie.go similarity index 100% rename from g/net/ghttp/http_server_cookie.go rename to g/net/ghttp/ghttp_server_cookie.go diff --git a/g/net/ghttp/http_server_domain.go b/g/net/ghttp/ghttp_server_domain.go similarity index 100% rename from g/net/ghttp/http_server_domain.go rename to g/net/ghttp/ghttp_server_domain.go diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go new file mode 100644 index 000000000..3c324ea92 --- /dev/null +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -0,0 +1,189 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package ghttp + +import ( + "fmt" + "net" + "os" + "syscall" + "context" + "net/http" + "os/signal" + "crypto/tls" + "gitee.com/johng/gf/g/os/glog" +) + +const ( + gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY = "GF_WEB_SERVER_GRACEFUL" + gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING = gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY + "=1" +) + +// 优雅的Web Server对象封装 +type gracefulServer struct { + httpServer *http.Server + listener net.Listener + isGraceful bool + signalChan chan os.Signal + shutdownChan chan bool +} + +// 创建一个优雅的Http Server +func (s *Server) newGracefulServer(addr string) *gracefulServer { + isGraceful := false + if os.Getenv(gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY) != "" { + isGraceful = true + } + return &gracefulServer { + httpServer : s.newServer(addr), + isGraceful : isGraceful, + signalChan : make(chan os.Signal), + shutdownChan : make(chan bool), + } +} + +// 执行HTTP监听 +func (s *gracefulServer) ListenAndServe() error { + addr := s.httpServer.Addr + ln, err := s.getNetListener(addr) + if err != nil { + return err + } + s.listener = ln + return s.doServe() +} + +// 执行HTTPS监听 +func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { + addr := s.httpServer.Addr + config := &tls.Config{} + if s.httpServer.TLSConfig != nil { + *config = *s.httpServer.TLSConfig + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + ln, err := s.getNetListener(addr) + if err != nil { + return err + } + s.listener = tls.NewListener(ln, config) + return s.doServe() +} + +// 开始执行Web Server服务处理 +func (s *gracefulServer) doServe() error { + go s.handleSignals() + err := s.httpServer.Serve(s.listener) + <-s.shutdownChan + return err +} + +// 自定义的net.Listener +func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { + var ln net.Listener + var err error + if s.isGraceful { + //path := fmt.Sprintf("%s%sgf.web.server.fd.%d", gfile.TempDir(), gfile.Separator,gtime.Nanosecond()) + //f, err := gfile.Open(path) + //if err != nil { + // return nil, err + //} + f := os.NewFile(3, "") + ln, err = net.FileListener(f) + if err != nil { + err = fmt.Errorf("net.FileListener error: %v", err) + return nil, err + } + } else { + ln, err = net.Listen("tcp", addr) + if err != nil { + err = fmt.Errorf("net.Listen error: %v", err) + return nil, err + } + } + return ln, nil +} + +// 处理终端信号指令监听 +func (s *gracefulServer) handleSignals() { + var sig os.Signal + + signal.Notify( + s.signalChan, + syscall.SIGTERM, + syscall.SIGUSR2, + ) + + for { + sig = <-s.signalChan + switch sig { + case syscall.SIGTERM: + glog.Println("received SIGTERM, graceful shutting down HTTP server") + s.shutdown() + + case syscall.SIGUSR2: + glog.Println("received SIGUSR2, graceful restarting HTTP server") + if pid, err := s.startNewProcess(); err != nil { + glog.Printf("start new process failed: %v, continue serving\n", err) + } else { + glog.Printf("start new process successfully, the new pid is %d\n", pid) + s.shutdown() + } + default: + } + } +} + +// 执行请求优雅关闭 +func (s *gracefulServer) shutdown() { + if err := s.httpServer.Shutdown(context.Background()); err != nil { + glog.Errorf("server shutdown error: %v\n", err) + } else { + glog.Println("server shutdown success") + s.shutdownChan <- true + } +} + +// 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 +func (s *gracefulServer) startNewProcess() (uintptr, error) { + listenerFd, err := s.getTCPListenerFd() + if err != nil { + return 0, fmt.Errorf("failed to get socket file descriptor: %v", err) + } + // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 + envs := []string{} + for _, value := range os.Environ() { + if value != gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING { + envs = append(envs, value) + } + } + envs = append(envs, gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING) + fork, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr { + Env : os.Environ(), + Files : []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd}, + }) + if err != nil { + return 0, fmt.Errorf("failed to forkexec: %v", err) + } + return uintptr(fork), nil +} + +// 获得对应net.TCPListener的文件描述符文件ID +func (s *gracefulServer) getTCPListenerFd() (uintptr, error) { + file, err := s.listener.(*net.TCPListener).File() + if err != nil { + return 0, err + } + return file.Fd(), nil +} diff --git a/g/net/ghttp/http_server_handler.go b/g/net/ghttp/ghttp_server_handler.go similarity index 100% rename from g/net/ghttp/http_server_handler.go rename to g/net/ghttp/ghttp_server_handler.go diff --git a/g/net/ghttp/http_server_hooks.go b/g/net/ghttp/ghttp_server_hooks.go similarity index 100% rename from g/net/ghttp/http_server_hooks.go rename to g/net/ghttp/ghttp_server_hooks.go diff --git a/g/net/ghttp/http_server_log.go b/g/net/ghttp/ghttp_server_log.go similarity index 100% rename from g/net/ghttp/http_server_log.go rename to g/net/ghttp/ghttp_server_log.go diff --git a/g/net/ghttp/http_server_options.go b/g/net/ghttp/ghttp_server_options.go similarity index 100% rename from g/net/ghttp/http_server_options.go rename to g/net/ghttp/ghttp_server_options.go diff --git a/g/net/ghttp/http_server_pprof.go b/g/net/ghttp/ghttp_server_pprof.go similarity index 100% rename from g/net/ghttp/http_server_pprof.go rename to g/net/ghttp/ghttp_server_pprof.go diff --git a/g/net/ghttp/http_server_router.go b/g/net/ghttp/ghttp_server_router.go similarity index 100% rename from g/net/ghttp/http_server_router.go rename to g/net/ghttp/ghttp_server_router.go diff --git a/g/net/ghttp/http_server_service.go b/g/net/ghttp/ghttp_server_service.go similarity index 100% rename from g/net/ghttp/http_server_service.go rename to g/net/ghttp/ghttp_server_service.go diff --git a/g/net/ghttp/http_server_session.go b/g/net/ghttp/ghttp_server_session.go similarity index 100% rename from g/net/ghttp/http_server_session.go rename to g/net/ghttp/ghttp_server_session.go diff --git a/g/net/ghttp/http_server_status.go b/g/net/ghttp/ghttp_server_status.go similarity index 100% rename from g/net/ghttp/http_server_status.go rename to g/net/ghttp/ghttp_server_status.go diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 2ab52d11c..4993dba69 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -462,4 +462,9 @@ func MainPkgPath() string { return p } return "" +} + +// 系统临时目录 +func TempDir() string { + return os.TempDir() } \ No newline at end of file diff --git a/geg/encoding/gbase64.go b/geg/encoding/gbase64.go new file mode 100644 index 000000000..416d35d1a --- /dev/null +++ b/geg/encoding/gbase64.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "gitee.com/johng/gf/g/encoding/gbase64" +) + +func main() { + s := "john" + b := gbase64.Encode(s) + c, e := gbase64.Decode(b) + fmt.Println(b) + fmt.Println(c) + fmt.Println(e) +} + + + diff --git a/geg/other/graceful.go b/geg/other/graceful.go new file mode 100644 index 000000000..dbe5b5168 --- /dev/null +++ b/geg/other/graceful.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/tabalt/gracehttp" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello world") + }) + + err := gracehttp.ListenAndServe(":8888", nil) + if err != nil { + fmt.Println(err) + } +} \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index 56d488a57..dbe5b5168 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,8 +2,18 @@ package main import ( "fmt" + "net/http" + + "github.com/tabalt/gracehttp" ) func main() { - fmt.Println("我要加入gf框架开发团队!!") -} + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello world") + }) + + err := gracehttp.ListenAndServe(":8888", nil) + if err != nil { + fmt.Println(err) + } +} \ No newline at end of file From 6f2e265a65466027a7f56233cb9bf6aca025e2b5 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 7 May 2018 18:44:58 +0800 Subject: [PATCH 02/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_graceful.go | 11 ++++++----- geg/other/test.go | 11 ++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 3c324ea92..a72d8dcd3 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -21,6 +21,7 @@ import ( const ( gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY = "GF_WEB_SERVER_GRACEFUL" gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING = gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY + "=1" + gGF_WEB_SERVER_GRACEFUL_LISTENER_FD = 3 ) // 优雅的Web Server对象封装 @@ -99,7 +100,7 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { //if err != nil { // return nil, err //} - f := os.NewFile(3, "") + f := os.NewFile(gGF_WEB_SERVER_GRACEFUL_LISTENER_FD, "") ln, err = net.FileListener(f) if err != nil { err = fmt.Errorf("net.FileListener error: %v", err) @@ -162,15 +163,15 @@ func (s *gracefulServer) startNewProcess() (uintptr, error) { return 0, fmt.Errorf("failed to get socket file descriptor: %v", err) } // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 - envs := []string{} + env := make([]string, 0) for _, value := range os.Environ() { if value != gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING { - envs = append(envs, value) + env = append(env, value) } } - envs = append(envs, gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING) + env = append(env, gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING) fork, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr { - Env : os.Environ(), + Env : env, Files : []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd}, }) if err != nil { diff --git a/geg/other/test.go b/geg/other/test.go index dbe5b5168..5a46ce481 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -7,13 +7,18 @@ import ( "github.com/tabalt/gracehttp" ) -func main() { + +func test() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) - - err := gracehttp.ListenAndServe(":8888", nil) + s := gracehttp.NewServer(":8888", nil, gracehttp.DEFAULT_READ_TIMEOUT, gracehttp.DEFAULT_WRITE_TIMEOUT) + err := s.ListenAndServe() if err != nil { fmt.Println(err) } +} + +func main() { + test() } \ No newline at end of file From 7ebf6825b6d7c271891f86f2f441375b7f11b91f Mon Sep 17 00:00:00 2001 From: John Date: Tue, 8 May 2018 18:41:29 +0800 Subject: [PATCH 03/31] =?UTF-8?q?=E7=83=AD=E9=87=8D=E5=90=AF=E7=89=B9?= =?UTF-8?q?=E6=80=A7=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 110 +++++++++++++++++++++++---- g/net/ghttp/ghttp_server_graceful.go | 103 ++++--------------------- g/os/gpm/gpm.go | 102 +++++++++++++++++++++++++ g/os/gpm/gpm_proccess.go | 90 ++++++++++++++++++++++ geg/other/test.go | 7 +- geg/other/test2.go | 24 ++++-- 6 files changed, 323 insertions(+), 113 deletions(-) create mode 100644 g/os/gpm/gpm.go create mode 100644 g/os/gpm/gpm_proccess.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index d8e6fcb8e..113808e96 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -19,6 +19,9 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" + "fmt" + "gitee.com/johng/gf/g/os/gpm" + "net" ) const ( @@ -26,10 +29,12 @@ const ( gDEFAULT_SERVER = "default" gDEFAULT_DOMAIN = "default" gDEFAULT_METHOD = "ALL" - gDEFAULT_COOKIE_PATH = "/" // 默认path - gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) - gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) - gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 + gDEFAULT_COOKIE_PATH = "/" // 默认path + gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) + gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) + gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 + gCHILD_ENVIRON_KEY = "GF_WEB_SERVER_CHILD" // 用以子父进程识别,环境变量名称 + gCHILD_ENVIRON_STRING = gCHILD_ENVIRON_KEY + "=1" // 用以子父进程识别,环境变量键值设置 ) // ghttp.Server结构体 @@ -38,7 +43,8 @@ type Server struct { name string // 服务名称,方便识别 config ServerConfig // 配置对象 status int8 // 当前服务器状态(0:未启动,1:运行中) - servers []*http.Server // 底层http.Server列表 + servers []*gracefulServer // 底层http.Server列表 + pmanager *gpm.Manager // 进程管理器,用于管理子进程服务 methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充) servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) @@ -110,7 +116,8 @@ func GetServer(name...interface{}) (*Server) { } s := &Server { name : sname, - servers : make([]*http.Server, 0), + servers : make([]*gracefulServer, 0), + pmanager : gpm.New(), methodsMap : make(map[string]bool), handlerMap : make(HandlerMap), statusHandlerMap : make(map[string]HandlerFunc), @@ -161,7 +168,9 @@ func (s *Server) Run() error { s.startCloseQueueLoop() // 开始执行底层Web Server创建,端口监听 - var wg sync.WaitGroup + var fd = 3 + var wg sync.WaitGroup + var server *gracefulServer if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { // HTTPS if len(s.config.HTTPSAddr) == 0 { @@ -175,12 +184,20 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - server := s.newGracefulServer(addr) + if s.isChildProcess() { + server = s.newGracefulServer(addr, fd) + fd++ + } else { + server = s.newGracefulServer(addr) + } if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { - glog.Error(err) + // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 + if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + glog.Error(err) + } wg.Done() } else { - //s.servers = append(s.servers, server) + s.servers = append(s.servers, server) } }(v) } @@ -193,12 +210,20 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - server := s.newGracefulServer(addr) + if s.isChildProcess() { + server = s.newGracefulServer(addr, fd) + fd++ + } else { + server = s.newGracefulServer(addr) + } if err := server.ListenAndServe(); err != nil { - glog.Error(err) + // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 + if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + glog.Error(err) + } wg.Done() } else { - //s.servers = append(s.servers, server) + s.servers = append(s.servers, server) } }(v) } @@ -210,6 +235,65 @@ func (s *Server) Run() error { return nil } +// 重启Web Server +func (s *Server) Restart() { + if pid, err := s.startChildProcess(); err != nil { + glog.Printf("server restart failed: %v, continue serving\n", err) + } else { + glog.Printf("server restart successfully, new pid: %d\n", pid) + + } +} + +// 关闭Web Server +func (s *Server) Shutdown() { + for _, v := range s.servers { + v. + if f, e := v.listener.(*net.TCPListener).File(); e == nil { + files = append(files, f) + } else { + return 0, fmt.Errorf("failed to get listener file: %v", e) + } + } +} + +// 判断是否为子进程执行 +func (s *Server) isChildProcess() bool { + return os.Getenv(gCHILD_ENVIRON_KEY) != "" +} + +// 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 +func (s *Server) startChildProcess() (int, error) { + if s.isChildProcess() { + return 0, errors.New("only main process can fork child process") + } + // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 + env := make([]string, 0) + for _, value := range os.Environ() { + if value != gCHILD_ENVIRON_STRING { + env = append(env, value) + } + } + env = append(env, gCHILD_ENVIRON_STRING) + // 获取所有http server的file + files := []*os.File{ os.Stdin,os.Stdout,os.Stderr} + for _, v := range s.servers { + if f, e := v.listener.(*net.TCPListener).File(); e == nil { + files = append(files, f) + } else { + return 0, fmt.Errorf("failed to get listener file: %v", e) + } + } + p, err := os.StartProcess(os.Args[0], os.Args, &os.ProcAttr { + Env : env, + Files : files, + }) + if err != nil { + return 0, fmt.Errorf("failed to forkexec: %v", err) + } + return p.Pid, nil +} + // 生成一个底层的Web Server对象 func (s *Server) newServer(addr string) *http.Server { return &http.Server { diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index a72d8dcd3..6ea8e1c30 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -10,41 +10,32 @@ import ( "fmt" "net" "os" - "syscall" "context" "net/http" - "os/signal" "crypto/tls" "gitee.com/johng/gf/g/os/glog" ) -const ( - gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY = "GF_WEB_SERVER_GRACEFUL" - gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING = gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY + "=1" - gGF_WEB_SERVER_GRACEFUL_LISTENER_FD = 3 -) - // 优雅的Web Server对象封装 type gracefulServer struct { + fd uintptr + addr string httpServer *http.Server listener net.Listener - isGraceful bool - signalChan chan os.Signal shutdownChan chan bool } // 创建一个优雅的Http Server -func (s *Server) newGracefulServer(addr string) *gracefulServer { - isGraceful := false - if os.Getenv(gGF_WEB_SERVER_GRACEFUL_ENVIRON_KEY) != "" { - isGraceful = true - } - return &gracefulServer { +func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer { + gs := &gracefulServer { + addr : addr, httpServer : s.newServer(addr), - isGraceful : isGraceful, - signalChan : make(chan os.Signal), shutdownChan : make(chan bool), } + if len(fd) > 0 && fd[0] > 0 { + gs.fd = uintptr(fd[0]) + } + return gs } // 执行HTTP监听 @@ -84,7 +75,6 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { // 开始执行Web Server服务处理 func (s *gracefulServer) doServe() error { - go s.handleSignals() err := s.httpServer.Serve(s.listener) <-s.shutdownChan return err @@ -94,13 +84,8 @@ func (s *gracefulServer) doServe() error { func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { var ln net.Listener var err error - if s.isGraceful { - //path := fmt.Sprintf("%s%sgf.web.server.fd.%d", gfile.TempDir(), gfile.Separator,gtime.Nanosecond()) - //f, err := gfile.Open(path) - //if err != nil { - // return nil, err - //} - f := os.NewFile(gGF_WEB_SERVER_GRACEFUL_LISTENER_FD, "") + if s.fd > 0 { + f := os.NewFile(s.fd, "") ln, err = net.FileListener(f) if err != nil { err = fmt.Errorf("net.FileListener error: %v", err) @@ -116,75 +101,13 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { return ln, nil } -// 处理终端信号指令监听 -func (s *gracefulServer) handleSignals() { - var sig os.Signal - - signal.Notify( - s.signalChan, - syscall.SIGTERM, - syscall.SIGUSR2, - ) - - for { - sig = <-s.signalChan - switch sig { - case syscall.SIGTERM: - glog.Println("received SIGTERM, graceful shutting down HTTP server") - s.shutdown() - - case syscall.SIGUSR2: - glog.Println("received SIGUSR2, graceful restarting HTTP server") - if pid, err := s.startNewProcess(); err != nil { - glog.Printf("start new process failed: %v, continue serving\n", err) - } else { - glog.Printf("start new process successfully, the new pid is %d\n", pid) - s.shutdown() - } - default: - } - } -} - // 执行请求优雅关闭 func (s *gracefulServer) shutdown() { if err := s.httpServer.Shutdown(context.Background()); err != nil { - glog.Errorf("server shutdown error: %v\n", err) + glog.Errorf("server %s shutdown error: %v\n", s.addr, err) } else { - glog.Println("server shutdown success") + glog.Printf("server %s shutdown successfully\n", s.addr) s.shutdownChan <- true } } -// 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 -func (s *gracefulServer) startNewProcess() (uintptr, error) { - listenerFd, err := s.getTCPListenerFd() - if err != nil { - return 0, fmt.Errorf("failed to get socket file descriptor: %v", err) - } - // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 - env := make([]string, 0) - for _, value := range os.Environ() { - if value != gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING { - env = append(env, value) - } - } - env = append(env, gGF_WEB_SERVER_GRACEFUL_ENVIRON_STRING) - fork, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr { - Env : env, - Files : []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listenerFd}, - }) - if err != nil { - return 0, fmt.Errorf("failed to forkexec: %v", err) - } - return uintptr(fork), nil -} - -// 获得对应net.TCPListener的文件描述符文件ID -func (s *gracefulServer) getTCPListenerFd() (uintptr, error) { - file, err := s.listener.(*net.TCPListener).File() - if err != nil { - return 0, err - } - return file.Fd(), nil -} diff --git a/g/os/gpm/gpm.go b/g/os/gpm/gpm.go new file mode 100644 index 000000000..1865c0d81 --- /dev/null +++ b/g/os/gpm/gpm.go @@ -0,0 +1,102 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +// 进程管理. +package gpm + +import ( + "os" + "gitee.com/johng/gf/g/container/gmap" +) + +// 进程管理器 +type Manager struct { + processes *gmap.IntInterfaceMap // 所管理的子进程map +} + +// 子进程 +type Process struct { + pm *Manager // 所属进程管理器 + path string // 可执行文件绝对路径 + args []string // 执行参数 + attr *os.ProcAttr // 进程属性 + process *os.Process // 底层进程对象 +} + +// 创建一个进程管理器 +func New () *Manager { + return &Manager{ + processes : gmap.NewIntInterfaceMap(), + } +} + +// 创建一个进程(不执行) +func (m *Manager) NewProcess(path string, args []string, env []string) *Process { + attr := &os.ProcAttr { + Env : env, + Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, + } + return &Process{ + pm : m, + path : path, + args : args, + attr : attr, + } +} + +// 获取一个进程 +func (m *Manager) GetProcess(pid int) *Process { + if v := m.processes.Get(pid); v != nil { + return v.(*Process) + } + return nil +} + +// 获取所有的进程对象,构成列表返回 +func (m *Manager) Processes() []*Process { + processes := make([]*Process, 0) + m.processes.RLockFunc(func(m map[int]interface{}) { + for _, v := range m { + processes = append(processes, v.(*Process)) + } + }) + return processes +} + +// 等待所有子进程结束 +func (m *Manager) WaitAll() { + processes := m.Processes() + if len(processes) > 0 { + for _, p := range processes { + p.Wait() + } + } +} + +// 关闭所有的进程 +func (m *Manager) KillAll() error { + for _, p := range m.Processes() { + if err := p.Kill(); err != nil { + return err + } + } + return nil +} + +// 向所有进程发送信号量 +func (m *Manager) SignalAll(sig os.Signal) error { + for _, p := range m.Processes() { + if err := p.Signal(sig); err != nil { + return err + } + } + return nil +} + +// 当前进程总数 +func (m *Manager) Size() int { + return m.processes.Size() +} \ No newline at end of file diff --git a/g/os/gpm/gpm_proccess.go b/g/os/gpm/gpm_proccess.go new file mode 100644 index 000000000..e0b505ca5 --- /dev/null +++ b/g/os/gpm/gpm_proccess.go @@ -0,0 +1,90 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package gpm + +import ( + "os" +) + +// 运行进程 +func (p *Process) Run() (int, error) { + if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { + p.process = process + p.pm.processes.Set(process.Pid, p) + return process.Pid, nil + } else { + return 0, err + } +} + +func (p *Process) SetArgs(args []string) { + p.args = args +} + +func (p *Process) AddArgs(args []string) { + for _, v := range args { + p.args = append(p.args, v) + } +} + +func (p *Process) SetEnv(env []string) { + p.attr.Env = env +} + +func (p *Process) AddEnv(env []string) { + for _, v := range env { + p.attr.Env = append(p.attr.Env, v) + } +} + +func (p *Process) SetAttr(attr *os.ProcAttr) { + p.attr = attr +} + +func (p *Process) GetAttr() *os.ProcAttr { + return p.attr +} + +// PID +func (p *Process) Pid() int { + if p.process != nil { + return p.process.Pid + } + return 0 +} + +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.process.Release() +} + +// Kill causes the Process to exit immediately. +func (p *Process) Kill() error { + if err := p.process.Kill(); err == nil { + p.pm.processes.Remove(p.Pid()) + return nil + } else { + return err + } +} + +// Wait waits for the Process to exit, and then returns a +// ProcessState describing its status and an error, if any. +// Wait releases any resources associated with the Process. +// On most operating systems, the Process must be a child +// of the current process or an error will be returned. +func (p *Process) Wait() (*os.ProcessState, error) { + return p.process.Wait() +} + +// Signal sends a signal to the Process. +// Sending Interrupt on Windows is not implemented. +func (p *Process) Signal(sig os.Signal) error { + return p.process.Signal(sig) +} \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index 5a46ce481..2483c275e 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/tabalt/gracehttp" + "os" ) @@ -20,5 +21,7 @@ func test() { } func main() { - test() -} \ No newline at end of file + fmt.Println(os.NewFile(11111, "")) + fmt.Println(os.NewFile(111111111, "")) + fmt.Println(os.NewFile(33333333333333, "")) +} diff --git a/geg/other/test2.go b/geg/other/test2.go index a3aceda50..6080a18d9 100644 --- a/geg/other/test2.go +++ b/geg/other/test2.go @@ -2,16 +2,24 @@ package main import ( "fmt" + "gitee.com/johng/gf/g/os/gpm" + "os" + "time" + "gitee.com/johng/gf/g/os/glog" ) func main() { - //var v interface{} - m := map[string]int { - "age" : 18, + m := gpm.New() + env := os.Environ() + env = append(env, "child=1") + p := m.NewProcess(os.Args[0], os.Args, env) + if os.Getenv("child") != "" { + time.Sleep(3*time.Second) + glog.Error("error") + } else { + pid, err := p.Run() + fmt.Println(pid) + fmt.Println(err) + fmt.Println(p.Wait()) } - //v = m - p := &m - (*p)["age"] = 16 - //fmt.Println(v) - fmt.Println(m) } \ No newline at end of file From 6b7d24ba98d6f5e246e77b126ff03fa70132476f Mon Sep 17 00:00:00 2001 From: John Date: Tue, 8 May 2018 23:38:09 +0800 Subject: [PATCH 04/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF/?= =?UTF-8?q?=E7=83=AD=E7=BC=96=E8=AF=91=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 171 +++++++++++++++++---------- g/net/ghttp/ghttp_server_cmd.go | 26 ++++ g/net/ghttp/ghttp_server_graceful.go | 14 ++- g/os/gpm/gpm.go | 29 +++-- g/os/gpm/gpm_proccess.go | 3 + geg/other/test.go | 11 +- 6 files changed, 182 insertions(+), 72 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_cmd.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 113808e96..a2aeba79d 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -22,19 +22,22 @@ import ( "fmt" "gitee.com/johng/gf/g/os/gpm" "net" + "os/signal" + "syscall" + "time" + "gitee.com/johng/gf/g/os/gcmd" ) const ( - gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" - gDEFAULT_SERVER = "default" - gDEFAULT_DOMAIN = "default" - gDEFAULT_METHOD = "ALL" - gDEFAULT_COOKIE_PATH = "/" // 默认path - gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) - gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) - gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 - gCHILD_ENVIRON_KEY = "GF_WEB_SERVER_CHILD" // 用以子父进程识别,环境变量名称 - gCHILD_ENVIRON_STRING = gCHILD_ENVIRON_KEY + "=1" // 用以子父进程识别,环境变量键值设置 + gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" + gDEFAULT_SERVER = "default" + gDEFAULT_DOMAIN = "default" + gDEFAULT_METHOD = "ALL" + gDEFAULT_COOKIE_PATH = "/" // 默认path + gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) + gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) + gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 + gDEFAULT_COMMAND_PORT = 336816 // 默认本地命令控制端口 ) // ghttp.Server结构体 @@ -44,7 +47,7 @@ type Server struct { config ServerConfig // 配置对象 status int8 // 当前服务器状态(0:未启动,1:运行中) servers []*gracefulServer // 底层http.Server列表 - pmanager *gpm.Manager // 进程管理器,用于管理子进程服务 + cmdPort int // 本地Web Server命令控制端口 methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充) servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) @@ -77,6 +80,9 @@ type Server struct { accessLogEnabled *gtype.Bool // 是否开启access log accessLogger *glog.Logger // access log日志对象 errorLogger *glog.Logger // error log日志对象 + // 多进程管理控制 + manager *gpm.Manager // 多进程管理 + heartbeats } // 域名、URI与回调函数的绑定记录表 @@ -117,7 +123,7 @@ func GetServer(name...interface{}) (*Server) { s := &Server { name : sname, servers : make([]*gracefulServer, 0), - pmanager : gpm.New(), + cmdPort : gDEFAULT_COMMAND_PORT, methodsMap : make(map[string]bool), handlerMap : make(HandlerMap), statusHandlerMap : make(map[string]HandlerFunc), @@ -139,6 +145,8 @@ func GetServer(name...interface{}) (*Server) { accessLogger : glog.New(), errorLogger : glog.New(), logHandler : gtype.NewInterface(), + manager : gpm.New(), + signalChan : make(chan os.Signal), } s.errorLogger.SetBacktraceSkip(4) s.accessLogger.SetBacktraceSkip(4) @@ -167,9 +175,26 @@ func (s *Server) Run() error { // 开启异步关闭队列处理循环 s.startCloseQueueLoop() + // 开启Web Server执行 + s.startServer() + return nil +} + +// 开启底层Web Server执行 +func (s *Server) startServer() { + // 主进程只负责创建子进程 + if !s.isChildProcess() { + s.forkChildProcess() + time.Sleep(10*time.Second) + time.Sleep(1000*time.Second) + return + } + // 信号量控制监听 + go s.handleSignals() // 开始执行底层Web Server创建,端口监听 var fd = 3 var wg sync.WaitGroup + var fcount = s.processFileCount() var server *gracefulServer if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { // HTTPS @@ -184,20 +209,19 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if s.isChildProcess() { + if s.isChildProcess() && fcount > 0 { server = s.newGracefulServer(addr, fd) fd++ } else { server = s.newGracefulServer(addr) } + s.servers = append(s.servers, server) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { glog.Error(err) } wg.Done() - } else { - s.servers = append(s.servers, server) } }(v) } @@ -210,20 +234,21 @@ func (s *Server) Run() error { for _, v := range array { wg.Add(1) go func(addr string) { - if s.isChildProcess() { + if s.isChildProcess() && fcount > 0 { server = s.newGracefulServer(addr, fd) fd++ } else { server = s.newGracefulServer(addr) } + s.servers = append(s.servers, server) if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + glog.Println(fd) + glog.Println(os.Args) glog.Error(err) } wg.Done() - } else { - s.servers = append(s.servers, server) } }(v) } @@ -232,51 +257,78 @@ func (s *Server) Run() error { // 阻塞执行,直到所有Web Server退出 wg.Wait() - return nil +} + +// 异步处理信号量监控 +func (s *Server) handleSignals() { + var sig os.Signal + + signal.Notify( + s.signalChan, + syscall.SIGTERM, + syscall.SIGUSR2, + ) + + for { + sig = <- s.signalChan + switch sig { + case syscall.SIGTERM: s.Shutdown() + case syscall.SIGUSR2: s.Restart() + default: + } + } } // 重启Web Server func (s *Server) Restart() { - if pid, err := s.startChildProcess(); err != nil { - glog.Printf("server restart failed: %v, continue serving\n", err) + // 如果是主进程,那么向所有子进程发送重启信号 + if !s.isChildProcess() { + s.manager.SignalAll(syscall.SIGUSR2) + return + } + if pid, err := s.forkChildProcess(); err != nil { + glog.Errorf("server restart failed: %v, continue serving\n", err) } else { glog.Printf("server restart successfully, new pid: %d\n", pid) - + s.Shutdown() } } // 关闭Web Server func (s *Server) Shutdown() { - for _, v := range s.servers { - v. - if f, e := v.listener.(*net.TCPListener).File(); e == nil { - files = append(files, f) - } else { - return 0, fmt.Errorf("failed to get listener file: %v", e) - } + // 如果是主进程,那么向所有子进程发送关闭信号 + if !s.isChildProcess() { + s.manager.SignalAll(syscall.SIGTERM) + return } + for _, v := range s.servers { + v.shutdown() + } +} + +// 子进程获取的文件打开数 +func (s *Server) processFileCount() int { + return gconv.Int(gcmd.Option.Get("fcount")) } // 判断是否为子进程执行 func (s *Server) isChildProcess() bool { - return os.Getenv(gCHILD_ENVIRON_KEY) != "" + return s.getTopId() > 0 +} + +// 获取顶级进程ID(管理进程ID) +func (s *Server) getTopId() int { + id := gcmd.Option.Get("topid") + if id != "" { + return gconv.Int(id) + } + return 0 } // 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 -func (s *Server) startChildProcess() (int, error) { - if s.isChildProcess() { - return 0, errors.New("only main process can fork child process") - } - // 构造子进程的环境变量,并增加环境变量参数以标识该进程是graceful子进程 - env := make([]string, 0) - for _, value := range os.Environ() { - if value != gCHILD_ENVIRON_STRING { - env = append(env, value) - } - } - env = append(env, gCHILD_ENVIRON_STRING) +func (s *Server) forkChildProcess() (int, error) { // 获取所有http server的file - files := []*os.File{ os.Stdin,os.Stdout,os.Stderr} + files := []*os.File{os.Stdin,os.Stdout,os.Stderr} for _, v := range s.servers { if f, e := v.listener.(*net.TCPListener).File(); e == nil { files = append(files, f) @@ -284,25 +336,22 @@ func (s *Server) startChildProcess() (int, error) { return 0, fmt.Errorf("failed to get listener file: %v", e) } } - p, err := os.StartProcess(os.Args[0], os.Args, &os.ProcAttr { - Env : env, - Files : files, - }) - if err != nil { - return 0, fmt.Errorf("failed to forkexec: %v", err) + // 开启子进程,并传递socket文件指针 + topId := s.getTopId() + if topId == 0 { + topId = os.Getpid() } - return p.Pid, nil -} - -// 生成一个底层的Web Server对象 -func (s *Server) newServer(addr string) *http.Server { - return &http.Server { - Addr : addr, - Handler : s.config.Handler, - ReadTimeout : s.config.ReadTimeout, - WriteTimeout : s.config.WriteTimeout, - IdleTimeout : s.config.IdleTimeout, - MaxHeaderBytes : s.config.MaxHeaderBytes, + args := make([]string, 4) + args[0] = os.Args[0] + args[1] = fmt.Sprintf("--name=%s", s.name) + args[2] = fmt.Sprintf("--port=%d", s.cmdPort) + args[3] = fmt.Sprintf("--fcount=%d", len(files) - 3) + p := s.manager.NewProcess(os.Args[0], args, os.Environ()) + p.GetAttr().Files = files + if pid, err := p.Run(); err != nil { + return 0, fmt.Errorf("failed to fork process: %v", err) + } else { + return pid, nil } } diff --git a/g/net/ghttp/ghttp_server_cmd.go b/g/net/ghttp/ghttp_server_cmd.go new file mode 100644 index 000000000..d975fc7a2 --- /dev/null +++ b/g/net/ghttp/ghttp_server_cmd.go @@ -0,0 +1,26 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package ghttp + +import ( + "fmt" + "gitee.com/johng/gf/g/net/gtcp" +) + +// 开启命令监听端口 +func (s *Server) startCmdService() { + s.BindHandler("/heartbeat", func(r *Request) { + + }) + s.BindHandler("/restart", func(r *Request) { + + }) + server := s.newGracefulServer(fmt.Sprintf("127.0.0.1:%d", s.cmdPort)) + if err := server.ListenAndServe(); err != nil { + + } +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 6ea8e1c30..70022128a 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -29,7 +29,7 @@ type gracefulServer struct { func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer { gs := &gracefulServer { addr : addr, - httpServer : s.newServer(addr), + httpServer : s.newHttpServer(addr), shutdownChan : make(chan bool), } if len(fd) > 0 && fd[0] > 0 { @@ -38,6 +38,18 @@ func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer { return gs } +// 生成一个底层的Web Server对象 +func (s *Server) newHttpServer(addr string) *http.Server { + return &http.Server { + Addr : addr, + Handler : s.config.Handler, + ReadTimeout : s.config.ReadTimeout, + WriteTimeout : s.config.WriteTimeout, + IdleTimeout : s.config.IdleTimeout, + MaxHeaderBytes : s.config.MaxHeaderBytes, + } +} + // 执行HTTP监听 func (s *gracefulServer) ListenAndServe() error { addr := s.httpServer.Addr diff --git a/g/os/gpm/gpm.go b/g/os/gpm/gpm.go index 1865c0d81..432988679 100644 --- a/g/os/gpm/gpm.go +++ b/g/os/gpm/gpm.go @@ -35,19 +35,18 @@ func New () *Manager { // 创建一个进程(不执行) func (m *Manager) NewProcess(path string, args []string, env []string) *Process { - attr := &os.ProcAttr { - Env : env, - Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, - } - return &Process{ + return &Process { pm : m, path : path, args : args, - attr : attr, + attr : &os.ProcAttr { + Env : env, + Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, + }, } } -// 获取一个进程 +// 获取当前进程管理器中的一个进程 func (m *Manager) GetProcess(pid int) *Process { if v := m.processes.Get(pid); v != nil { return v.(*Process) @@ -55,6 +54,22 @@ func (m *Manager) GetProcess(pid int) *Process { return nil } +// 添加一个已存在的进程到管理器中 +func (m *Manager) AddProcess(pid int) *Process { + if v := m.GetProcess(pid); v != nil { + return v + } + if process, err := os.FindProcess(pid); err == nil { + p := &Process { + pm : m, + process : process, + } + m.processes.Set(pid, p) + return p + } + return nil +} + // 获取所有的进程对象,构成列表返回 func (m *Manager) Processes() []*Process { processes := make([]*Process, 0) diff --git a/g/os/gpm/gpm_proccess.go b/g/os/gpm/gpm_proccess.go index e0b505ca5..7a9502b98 100644 --- a/g/os/gpm/gpm_proccess.go +++ b/g/os/gpm/gpm_proccess.go @@ -12,6 +12,9 @@ import ( // 运行进程 func (p *Process) Run() (int, error) { + if p.process != nil { + return p.Pid(), nil + } if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { p.process = process p.pm.processes.Set(process.Pid, p) diff --git a/geg/other/test.go b/geg/other/test.go index 2483c275e..ee6c83601 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -6,6 +6,8 @@ import ( "github.com/tabalt/gracehttp" "os" + "gitee.com/johng/gf/g/os/gpm" + "time" ) @@ -21,7 +23,10 @@ func test() { } func main() { - fmt.Println(os.NewFile(11111, "")) - fmt.Println(os.NewFile(111111111, "")) - fmt.Println(os.NewFile(33333333333333, "")) + m := gpm.New() + args := os.Args + args = append(args, "--child=1") + p := m.NewProcess(args[0], args, nil) + p.Run() + time.Sleep(100*time.Second) } From e905677b665754b6e379ac16ad4a4221a897e3fc Mon Sep 17 00:00:00 2001 From: John Date: Wed, 9 May 2018 18:29:46 +0800 Subject: [PATCH 05/31] =?UTF-8?q?=E5=A2=9E=E5=8A=A0gflock=E5=8C=85?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E6=96=87=E4=BB=B6=E9=94=81=E7=89=B9=E6=80=A7?= =?UTF-8?q?=EF=BC=8Cgproc=E5=8C=85=E8=BF=9B=E7=A8=8B=E9=97=B4=E9=80=9A?= =?UTF-8?q?=E4=BF=A1=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 4 +- g/net/gtcp/tcp_func.go | 92 +++++++++++++++++++ g/os/gflock/gflock.go | 29 ++++++ g/os/gproc/gproc.go | 82 +++++++++++++++++ g/os/{gpm/gpm.go => gproc/gproc_manager.go} | 65 +++++++------ .../gproc_proccess.go} | 39 +++++++- geg/os/gflock/gflock.go | 25 +++++ geg/other/test.go | 2 +- geg/other/test2.go | 2 +- 9 files changed, 309 insertions(+), 31 deletions(-) create mode 100644 g/net/gtcp/tcp_func.go create mode 100644 g/os/gflock/gflock.go create mode 100644 g/os/gproc/gproc.go rename g/os/{gpm/gpm.go => gproc/gproc_manager.go} (63%) rename g/os/{gpm/gpm_proccess.go => gproc/gproc_proccess.go} (66%) create mode 100644 geg/os/gflock/gflock.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index a2aeba79d..d0216ec09 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -81,7 +81,7 @@ type Server struct { accessLogger *glog.Logger // access log日志对象 errorLogger *glog.Logger // error log日志对象 // 多进程管理控制 - manager *gpm.Manager // 多进程管理 + manager *gproc.Manager // 多进程管理 heartbeats } @@ -145,7 +145,7 @@ func GetServer(name...interface{}) (*Server) { accessLogger : glog.New(), errorLogger : glog.New(), logHandler : gtype.NewInterface(), - manager : gpm.New(), + manager : gproc.New(), signalChan : make(chan os.Signal), } s.errorLogger.SetBacktraceSkip(4) diff --git a/g/net/gtcp/tcp_func.go b/g/net/gtcp/tcp_func.go new file mode 100644 index 000000000..d25db129d --- /dev/null +++ b/g/net/gtcp/tcp_func.go @@ -0,0 +1,92 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package gtcp + +import ( + "io" + "net" + "time" +) + +const ( + gDEFAULT_RETRY_INTERVAL = 100 // 默认重试时间间隔 +) + +type Retry struct { + Count int // 重试次数 + Interval int // 重试间隔(毫秒) +} + +// 常见的二进制数据校验方式,生成校验结果 +func Checksum(buffer []byte) uint32 { + var checksum uint32 + for _, b := range buffer { + checksum += uint32(b) + } + return checksum +} + +// 获取数据 +func Receive(conn net.Conn, retry...Retry) []byte { + size := 1024 + data := make([]byte, 0) + for { + buffer := make([]byte, size) + length, err := conn.Read(buffer) + if length < 1 && err != nil { + if err == io.EOF || len(retry) == 0 || retry[0].Count == 0 { + break + } + if len(retry) > 0 { + retry[0].Count-- + if retry[0].Interval == 0 { + retry[0].Interval = gDEFAULT_RETRY_INTERVAL + } + time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) + } + } else { + data = append(data, buffer[0:length]...) + if err == io.EOF { + break + } + } + } + return data +} + +// 带超时时间的数据获取 +func ReceiveWithTimeout(conn net.Conn, timeout time.Duration, retry...Retry) []byte { + conn.SetReadDeadline(time.Now().Add(timeout)) + return Receive(conn, retry...) +} + +// 发送数据 +func Send(conn net.Conn, data []byte, retry...Retry) error { + for { + _, err := conn.Write(data) + if err != nil { + if len(retry) == 0 || retry[0].Count == 0 { + return err + } + if len(retry) > 0 { + retry[0].Count-- + if retry[0].Interval == 0 { + retry[0].Interval = gDEFAULT_RETRY_INTERVAL + } + time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) + } + } else { + return nil + } + } +} + +// 带超时时间的数据发送 +func SendWithTimeout(conn net.Conn, data []byte, timeout time.Duration, retry...Retry) error { + conn.SetWriteDeadline(time.Now().Add(timeout)) + return Send(conn, data, retry...) +} \ No newline at end of file diff --git a/g/os/gflock/gflock.go b/g/os/gflock/gflock.go new file mode 100644 index 000000000..08e5b2c8a --- /dev/null +++ b/g/os/gflock/gflock.go @@ -0,0 +1,29 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +// 文件锁. +package gflock + +import ( + "github.com/theckman/go-flock" + "gitee.com/johng/gf/g/os/gfile" +) + +// 文件锁 +type Locker struct { + flock *flock.Flock +} + +// 创建文件锁 +func New(file string) *Locker { + path := gfile.TempDir() + gfile.Separator + file + lock := flock.NewFlock(path) + return &Locker{ + lock, + } +} + + diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go new file mode 100644 index 000000000..74735533d --- /dev/null +++ b/g/os/gproc/gproc.go @@ -0,0 +1,82 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +// 进程管理. +package gproc + +import ( + "os" + "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/net/gtcp" + "net" +) + +const ( + gCOMMUNICATION_MAIN_PORT = 30000 + gCOMMUNICATION_CHILD_PORT = 40000 + gCHILD_PROCESS_ENV_KEY = "gf.process.manager.child" + gCHILD_PROCESS_ENV_STRING = gCHILD_PROCESS_ENV_KEY + "=1" +) + +// TCP通信数据结构定义 +type Msg struct { + Pid int // PID,哪个进程发送的消息 + Data []byte // 参数,消息附带的参数 +} + +// 获取其他进程传递到当前进程的消息包,阻塞执行 +func GetMsg() *Msg { + if v := msgQueue.PopFront(); v != nil { + return v.(*Msg) + } + return nil +} + +// 判断当前进程是否为gproc创建的子进程 +func IsChild() bool { + return os.Getenv(gCHILD_PROCESS_ENV_KEY) != "" +} + + +// TCP数据通信处理回调函数 +// 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) +func tcpServiceHandler(conn net.Conn) { + buffer := gtcp.Receive(conn, gtcp.Retry{3, 100}) + msgs := bufferToMsgs(buffer) + if len(msgs) == 0 { + conn.Close() + return + } + for _, msg := range msgs { + msgQueue.PushBack(msg) + } +} + +// 数据解包,防止黏包 +func bufferToMsgs(buffer []byte) []*Msg { + s := 0 + msgs := make([]*Msg, 0) + for s < len(buffer) { + length := gbinary.DecodeToInt(buffer[s : 4]) + if length < 0 || length > len(buffer) { + s++ + continue + } + checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) + checksum2 := gtcp.Checksum(buffer[s + 12 : s + length]) + if checksum1 != checksum2 { + s++ + continue + } + msgs = append(msgs, &Msg { + Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), + Data : buffer[s + 12 : s + length], + }) + s += length + } + return msgs +} diff --git a/g/os/gpm/gpm.go b/g/os/gproc/gproc_manager.go similarity index 63% rename from g/os/gpm/gpm.go rename to g/os/gproc/gproc_manager.go index 432988679..c8ab8b235 100644 --- a/g/os/gpm/gpm.go +++ b/g/os/gproc/gproc_manager.go @@ -5,11 +5,17 @@ // You can obtain one at https://gitee.com/johng/gf. // 进程管理. -package gpm +package gproc import ( "os" + "net" + "fmt" + "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/net/gtcp" "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/container/gqueue" ) // 进程管理器 @@ -17,14 +23,8 @@ type Manager struct { processes *gmap.IntInterfaceMap // 所管理的子进程map } -// 子进程 -type Process struct { - pm *Manager // 所属进程管理器 - path string // 可执行文件绝对路径 - args []string // 执行参数 - attr *os.ProcAttr // 进程属性 - process *os.Process // 底层进程对象 -} +// 进程通信消息队列 +var msgQueue = gqueue.New() // 创建一个进程管理器 func New () *Manager { @@ -33,8 +33,37 @@ func New () *Manager { } } +// 创建主进程与子进程的TCP通信监听服务 +func (m *Manager) startTcpService() { + go func() { + var listen *net.TCPListener + for i := gCOMMUNICATION_MAIN_PORT; i < gCOMMUNICATION_MAIN_PORT + 10000; i++ { + addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("127.0.0.1:%d", i)) + if err != nil { + continue + } + listen, err = net.ListenTCP("tcp", addr) + if err != nil { + continue + } + } + for { + if conn, err := listen.Accept(); err != nil { + glog.Error(err) + } else if conn != nil { + go tcpServiceHandler(conn) + } + } + }() +} + // 创建一个进程(不执行) -func (m *Manager) NewProcess(path string, args []string, env []string) *Process { +func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { + env := make([]string, len(environment) + 1) + for k, v := range environment { + env[k] = v + } + env[len(env)] = gCHILD_PROCESS_ENV_STRING return &Process { pm : m, path : path, @@ -54,22 +83,6 @@ func (m *Manager) GetProcess(pid int) *Process { return nil } -// 添加一个已存在的进程到管理器中 -func (m *Manager) AddProcess(pid int) *Process { - if v := m.GetProcess(pid); v != nil { - return v - } - if process, err := os.FindProcess(pid); err == nil { - p := &Process { - pm : m, - process : process, - } - m.processes.Set(pid, p) - return p - } - return nil -} - // 获取所有的进程对象,构成列表返回 func (m *Manager) Processes() []*Process { processes := make([]*Process, 0) diff --git a/g/os/gpm/gpm_proccess.go b/g/os/gproc/gproc_proccess.go similarity index 66% rename from g/os/gpm/gpm_proccess.go rename to g/os/gproc/gproc_proccess.go index 7a9502b98..b6a85b523 100644 --- a/g/os/gpm/gpm_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -4,12 +4,25 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://gitee.com/johng/gf. -package gpm +package gproc import ( "os" + "fmt" + "gitee.com/johng/gf/g/os/glog" + "net" + "gitee.com/johng/gf/g/net/gtcp" ) +// 子进程 +type Process struct { + pm *Manager // 所属进程管理器 + path string // 可执行文件绝对路径 + args []string // 执行参数 + attr *os.ProcAttr // 进程属性 + process *os.Process // 底层进程对象 +} + // 运行进程 func (p *Process) Run() (int, error) { if p.process != nil { @@ -24,6 +37,30 @@ func (p *Process) Run() (int, error) { } } +// 创建主进程与子进程的TCP通信监听服务 +func (p *Process) startTcpService() { + go func() { + var listen *net.TCPListener + for i := gCOMMUNICATION_CHILD_PORT; i < gCOMMUNICATION_CHILD_PORT + 10000; i++ { + addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("127.0.0.1:%d", i)) + if err != nil { + continue + } + listen, err = net.ListenTCP("tcp", addr) + if err != nil { + continue + } + } + for { + if conn, err := listen.Accept(); err != nil { + glog.Error(err) + } else if conn != nil { + go tcpServiceHandler(conn) + } + } + }() +} + func (p *Process) SetArgs(args []string) { p.args = args } diff --git a/geg/os/gflock/gflock.go b/geg/os/gflock/gflock.go new file mode 100644 index 000000000..4a936a715 --- /dev/null +++ b/geg/os/gflock/gflock.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/theckman/go-flock" + "fmt" + "time" +) + +func main() { + fileLock := flock.NewFlock("/var/lock/go-lock.lock") + fmt.Println(fileLock.Lock()) + fmt.Println(fileLock.Lock()) + time.Sleep(1000*time.Second) +//fmt.Println(locked) +// fmt.Println(fileLock.Locked()) +//fmt.Println(err) +// if err != nil { +// // handle locking error +// } +// +// if locked { +// // do work +// fileLock.Unlock() +// } +} diff --git a/geg/other/test.go b/geg/other/test.go index ee6c83601..e57a6b864 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -23,7 +23,7 @@ func test() { } func main() { - m := gpm.New() + m := gproc.New() args := os.Args args = append(args, "--child=1") p := m.NewProcess(args[0], args, nil) diff --git a/geg/other/test2.go b/geg/other/test2.go index 6080a18d9..dfeab4a61 100644 --- a/geg/other/test2.go +++ b/geg/other/test2.go @@ -9,7 +9,7 @@ import ( ) func main() { - m := gpm.New() + m := gproc.New() env := os.Environ() env = append(env, "child=1") p := m.NewProcess(os.Args[0], os.Args, env) From 44b9cb77ec1cffdb8da42ced29b2a4b96fbf00e6 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 10 May 2018 12:04:41 +0800 Subject: [PATCH 06/31] =?UTF-8?q?gflock=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/os/gflock/internals/flock/LICENSE | 27 ++++ g/os/gflock/internals/flock/flock.go | 109 +++++++++++++ g/os/gflock/internals/flock/flock_unix.go | 159 +++++++++++++++++++ g/os/gflock/internals/flock/flock_winapi.go | 76 +++++++++ g/os/gflock/internals/flock/flock_windows.go | 140 ++++++++++++++++ geg/os/gflock/gflock.go | 8 +- 6 files changed, 515 insertions(+), 4 deletions(-) create mode 100644 g/os/gflock/internals/flock/LICENSE create mode 100644 g/os/gflock/internals/flock/flock.go create mode 100644 g/os/gflock/internals/flock/flock_unix.go create mode 100644 g/os/gflock/internals/flock/flock_winapi.go create mode 100644 g/os/gflock/internals/flock/flock_windows.go diff --git a/g/os/gflock/internals/flock/LICENSE b/g/os/gflock/internals/flock/LICENSE new file mode 100644 index 000000000..aff7d358e --- /dev/null +++ b/g/os/gflock/internals/flock/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of linode-netint nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/g/os/gflock/internals/flock/flock.go b/g/os/gflock/internals/flock/flock.go new file mode 100644 index 000000000..d04059748 --- /dev/null +++ b/g/os/gflock/internals/flock/flock.go @@ -0,0 +1,109 @@ +// From & Thanks: https://github.com/theckman/go-flock +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Package flock implements a thread-safe sync.Locker interface for file locking. +// It also includes a non-blocking TryLock() function to allow locking +// without blocking execution. +// +// Package flock is released under the BSD 3-Clause License. See the LICENSE file +// for more details. +// +// While using this library, remember that the locking behaviors are not +// guaranteed to be the same on each platform. For example, some UNIX-like +// operating systems will transparently convert a shared lock to an exclusive +// lock. If you Unlock() the flock from a location where you believe that you +// have the shared lock, you may accidently drop the exclusive lock. +package flock + +import ( + "context" + "os" + "sync" + "time" +) + +// Flock is the struct type to handle file locking. All fields are unexported, +// with access to some of the fields provided by getter methods (Path() and Locked()). +type Flock struct { + mu sync.RWMutex // 用于接口层的阻塞互斥锁 + path string + m sync.RWMutex + fh *os.File + l bool + r bool +} + +// NewFlock is a function to return a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +func NewFlock(path string) *Flock { + return &Flock{path: path} +} + +// Path is a function to return the path as provided in NewFlock(). +func (f *Flock) Path() string { + return f.path +} + +// Locked is a function to return the current lock state (locked: true, unlocked: false). +func (f *Flock) Locked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.l +} + +// RLocked is a function to return the current read lock state (locked: true, unlocked: false). +func (f *Flock) RLocked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.r +} + +func (f *Flock) String() string { + return f.path +} + +// TryLockContext repeatedly tries to take an exclusive lock until one of the +// conditions is met: TryLock succeeds, TryLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(f.TryLock, ctx, retryDelay) +} + +// TryRLockContext repeatedly tries to take a shared lock until one of the +// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(f.TryRLock, ctx, retryDelay) +} + +func tryCtx(fn func() (bool, error), ctx context.Context, retryDelay time.Duration) (bool, error) { + if ctx.Err() != nil { + return false, ctx.Err() + } + for { + if ok, err := fn(); ok || err != nil { + return ok, err + } + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-time.After(retryDelay): + // try again + } + } +} + +func (f *Flock) setFh() error { + // open a new os.File instance + // create it if it doesn't exist, and open the file read-only. + fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDONLY, os.FileMode(0600)) + if err != nil { + return err + } + + // set the filehandle on the struct + f.fh = fh + return nil +} diff --git a/g/os/gflock/internals/flock/flock_unix.go b/g/os/gflock/internals/flock/flock_unix.go new file mode 100644 index 000000000..8aa896f80 --- /dev/null +++ b/g/os/gflock/internals/flock/flock_unix.go @@ -0,0 +1,159 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build !windows + +package flock + +import ( + "syscall" +) + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), this may transparently replace the +// shared lock with an exclusive lock on some UNIX-like operating systems. Be +// careful when using exclusive locks in conjunction with shared locks +// (RLock()), because calling Unlock() may accidentally release the exclusive +// lock that was once a shared lock. +func (f *Flock) Lock() error { + f.mu.Lock() + err := f.lock(&f.l, syscall.LOCK_EX) + if err != nil { + f.mu.Unlock() + return err + } + return nil +} + +// RLock is a blocking call to try and take a ahred file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + f.mu.RLock() + err := f.lock(&f.r, syscall.LOCK_SH) + if err != nil { + f.mu.RUnlock() + return err + } + return nil +} + +func (f *Flock) lock(locked *bool, flag int) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + } + + if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { + return err + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// syscall.LOCK_UN on the file and closes the file descriptor. It does not +// remove the file from disk. It's up to your application to do. +// +// Please note, if your shared lock became an exclusive lock this may +// unintentionally drop the exclusive lock if called by the consumer that +// believes they have a shared lock. Please see Lock() for more details. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + f.mu.Unlock() + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, syscall.LOCK_EX) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, syscall.LOCK_SH) +} + +func (f *Flock) try(locked *bool, flag int) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + } + + err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) + + switch err { + case syscall.EWOULDBLOCK: + return false, nil + case nil: + *locked = true + return true, nil + } + + return false, err +} diff --git a/g/os/gflock/internals/flock/flock_winapi.go b/g/os/gflock/internals/flock/flock_winapi.go new file mode 100644 index 000000000..fe405a255 --- /dev/null +++ b/g/os/gflock/internals/flock/flock_winapi.go @@ -0,0 +1,76 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build windows + +package flock + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32, _ = syscall.LoadLibrary("kernel32.dll") + procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") + procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") +) + +const ( + winLockfileFailImmediately = 0x00000001 + winLockfileExclusiveLock = 0x00000002 + winLockfileSharedLock = 0x00000000 +) + +// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows +// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: +// +// > The function requests an exclusive lock. Otherwise, it requests a shared +// > lock. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + +func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procLockFileEx), + 6, + uintptr(handle), + uintptr(flags), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset))) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} + +func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procUnlockFileEx), + 5, + uintptr(handle), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset)), + 0) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} diff --git a/g/os/gflock/internals/flock/flock_windows.go b/g/os/gflock/internals/flock/flock_windows.go new file mode 100644 index 000000000..a0103f6da --- /dev/null +++ b/g/os/gflock/internals/flock/flock_windows.go @@ -0,0 +1,140 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock + +import ( + "syscall" +) + +// ErrorLockViolation is the error code returned from the Windows syscall when a +// lock would block and you ask to fail immediately. +const ErrorLockViolation syscall.Errno = 0x21 // 33 + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, winLockfileExclusiveLock) +} + +// RLock is a blocking call to try and take a sahred file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, winLockfileSharedLock) +} + +func (f *Flock) lock(locked *bool, flag uint32) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + } + + if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// UnlockFileEx() on the file and closes the file descriptor. It does not remove +// the file from disk. It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking an exlusive file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, winLockfileExclusiveLock) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being shared-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, winLockfileSharedLock) +} + +func (f *Flock) try(locked *bool, flag uint32) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + } + + _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) + + if errNo > 0 { + if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { + return false, nil + } + + return false, errNo + } + + *locked = true + + return true, nil +} diff --git a/geg/os/gflock/gflock.go b/geg/os/gflock/gflock.go index 4a936a715..2fc22fc97 100644 --- a/geg/os/gflock/gflock.go +++ b/geg/os/gflock/gflock.go @@ -3,14 +3,14 @@ package main import ( "github.com/theckman/go-flock" "fmt" - "time" ) func main() { fileLock := flock.NewFlock("/var/lock/go-lock.lock") - fmt.Println(fileLock.Lock()) - fmt.Println(fileLock.Lock()) - time.Sleep(1000*time.Second) + + fmt.Println(fileLock.TryLock()) + fmt.Println(fileLock.TryRLock()) + //time.Sleep(1000*time.Second) //fmt.Println(locked) // fmt.Println(fileLock.Locked()) //fmt.Println(err) From dc20caeb272297cb2eb7ede39e88c5c425f72362 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 10 May 2018 16:07:14 +0800 Subject: [PATCH 07/31] =?UTF-8?q?=E5=AE=8C=E6=88=90gproc=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8F=8A=E9=80=9A=E4=BF=A1=E5=8C=85=20&=20gf?= =?UTF-8?q?lock=E6=96=87=E4=BB=B6=E9=94=81=E5=8C=85=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/os/gfile/gfile.go | 5 + g/os/gflock/gflock.go | 24 ++- g/os/gflock/internals/flock/LICENSE | 27 ---- g/os/gflock/internals/flock/flock.go | 109 ------------- g/os/gflock/internals/flock/flock_unix.go | 159 ------------------- g/os/gflock/internals/flock/flock_winapi.go | 76 --------- g/os/gflock/internals/flock/flock_windows.go | 140 ---------------- g/os/gfsnotify/gfsnotify.go | 4 +- g/os/gproc/gproc.go | 67 ++------ g/os/gproc/gproc_comm.go | 112 +++++++++++++ g/os/gproc/gproc_manager.go | 51 ++---- g/os/gproc/gproc_proccess.go | 38 ++--- geg/os/gproc/gproc.go | 27 ++++ geg/os/gproc/gproc2.go | 11 ++ 14 files changed, 212 insertions(+), 638 deletions(-) delete mode 100644 g/os/gflock/internals/flock/LICENSE delete mode 100644 g/os/gflock/internals/flock/flock.go delete mode 100644 g/os/gflock/internals/flock/flock_unix.go delete mode 100644 g/os/gflock/internals/flock/flock_winapi.go delete mode 100644 g/os/gflock/internals/flock/flock_windows.go create mode 100644 g/os/gproc/gproc_comm.go create mode 100644 geg/os/gproc/gproc.go create mode 100644 geg/os/gproc/gproc2.go diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 4993dba69..1efde864e 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -320,6 +320,11 @@ func putContents(path string, data []byte, flag int, perm os.FileMode) error { return nil } +// Truncate +func Truncate(path string, size int) error { + return os.Truncate(path, int64(size)) +} + // (文本)写入文件内容 func PutContents(path string, content string) error { return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) diff --git a/g/os/gflock/gflock.go b/g/os/gflock/gflock.go index 08e5b2c8a..b59718b4b 100644 --- a/g/os/gflock/gflock.go +++ b/g/os/gflock/gflock.go @@ -8,22 +8,42 @@ package gflock import ( + "sync" "github.com/theckman/go-flock" "gitee.com/johng/gf/g/os/gfile" ) // 文件锁 type Locker struct { + mu sync.RWMutex flock *flock.Flock } // 创建文件锁 func New(file string) *Locker { - path := gfile.TempDir() + gfile.Separator + file + path := gfile.TempDir() + gfile.Separator + "gflock" + gfile.Separator + file lock := flock.NewFlock(path) return &Locker{ - lock, + flock : lock, } } +func (l *Locker) Lock() { + l.mu.Lock() + l.flock.Lock() +} +func (l *Locker) UnLock() { + l.flock.Unlock() + l.mu.Unlock() +} + +func (l *Locker) RLock() { + l.mu.RLock() + l.flock.RLock() +} + +func (l *Locker) RUnlock() { + l.flock.Unlock() + l.mu.RUnlock() +} diff --git a/g/os/gflock/internals/flock/LICENSE b/g/os/gflock/internals/flock/LICENSE deleted file mode 100644 index aff7d358e..000000000 --- a/g/os/gflock/internals/flock/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015, Tim Heckman -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of linode-netint nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/g/os/gflock/internals/flock/flock.go b/g/os/gflock/internals/flock/flock.go deleted file mode 100644 index d04059748..000000000 --- a/g/os/gflock/internals/flock/flock.go +++ /dev/null @@ -1,109 +0,0 @@ -// From & Thanks: https://github.com/theckman/go-flock -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// Package flock implements a thread-safe sync.Locker interface for file locking. -// It also includes a non-blocking TryLock() function to allow locking -// without blocking execution. -// -// Package flock is released under the BSD 3-Clause License. See the LICENSE file -// for more details. -// -// While using this library, remember that the locking behaviors are not -// guaranteed to be the same on each platform. For example, some UNIX-like -// operating systems will transparently convert a shared lock to an exclusive -// lock. If you Unlock() the flock from a location where you believe that you -// have the shared lock, you may accidently drop the exclusive lock. -package flock - -import ( - "context" - "os" - "sync" - "time" -) - -// Flock is the struct type to handle file locking. All fields are unexported, -// with access to some of the fields provided by getter methods (Path() and Locked()). -type Flock struct { - mu sync.RWMutex // 用于接口层的阻塞互斥锁 - path string - m sync.RWMutex - fh *os.File - l bool - r bool -} - -// NewFlock is a function to return a new instance of *Flock. The only parameter -// it takes is the path to the desired lockfile. -func NewFlock(path string) *Flock { - return &Flock{path: path} -} - -// Path is a function to return the path as provided in NewFlock(). -func (f *Flock) Path() string { - return f.path -} - -// Locked is a function to return the current lock state (locked: true, unlocked: false). -func (f *Flock) Locked() bool { - f.m.RLock() - defer f.m.RUnlock() - return f.l -} - -// RLocked is a function to return the current read lock state (locked: true, unlocked: false). -func (f *Flock) RLocked() bool { - f.m.RLock() - defer f.m.RUnlock() - return f.r -} - -func (f *Flock) String() string { - return f.path -} - -// TryLockContext repeatedly tries to take an exclusive lock until one of the -// conditions is met: TryLock succeeds, TryLock fails with error, or Context -// Done channel is closed. -func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { - return tryCtx(f.TryLock, ctx, retryDelay) -} - -// TryRLockContext repeatedly tries to take a shared lock until one of the -// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context -// Done channel is closed. -func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { - return tryCtx(f.TryRLock, ctx, retryDelay) -} - -func tryCtx(fn func() (bool, error), ctx context.Context, retryDelay time.Duration) (bool, error) { - if ctx.Err() != nil { - return false, ctx.Err() - } - for { - if ok, err := fn(); ok || err != nil { - return ok, err - } - select { - case <-ctx.Done(): - return false, ctx.Err() - case <-time.After(retryDelay): - // try again - } - } -} - -func (f *Flock) setFh() error { - // open a new os.File instance - // create it if it doesn't exist, and open the file read-only. - fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDONLY, os.FileMode(0600)) - if err != nil { - return err - } - - // set the filehandle on the struct - f.fh = fh - return nil -} diff --git a/g/os/gflock/internals/flock/flock_unix.go b/g/os/gflock/internals/flock/flock_unix.go deleted file mode 100644 index 8aa896f80..000000000 --- a/g/os/gflock/internals/flock/flock_unix.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// +build !windows - -package flock - -import ( - "syscall" -) - -// Lock is a blocking call to try and take an exclusive file lock. It will wait -// until it is able to obtain the exclusive file lock. It's recommended that -// TryLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already exclusive-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -// -// If the *Flock has a shared lock (RLock), this may transparently replace the -// shared lock with an exclusive lock on some UNIX-like operating systems. Be -// careful when using exclusive locks in conjunction with shared locks -// (RLock()), because calling Unlock() may accidentally release the exclusive -// lock that was once a shared lock. -func (f *Flock) Lock() error { - f.mu.Lock() - err := f.lock(&f.l, syscall.LOCK_EX) - if err != nil { - f.mu.Unlock() - return err - } - return nil -} - -// RLock is a blocking call to try and take a ahred file lock. It will wait -// until it is able to obtain the shared file lock. It's recommended that -// TryRLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already shared-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) RLock() error { - f.mu.RLock() - err := f.lock(&f.r, syscall.LOCK_SH) - if err != nil { - f.mu.RUnlock() - return err - } - return nil -} - -func (f *Flock) lock(locked *bool, flag int) error { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return err - } - } - - if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { - return err - } - - *locked = true - return nil -} - -// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so -// while it is running the Locked() and RLocked() functions will be blocked. -// -// This function short-circuits if we are unlocked already. If not, it calls -// syscall.LOCK_UN on the file and closes the file descriptor. It does not -// remove the file from disk. It's up to your application to do. -// -// Please note, if your shared lock became an exclusive lock this may -// unintentionally drop the exclusive lock if called by the consumer that -// believes they have a shared lock. Please see Lock() for more details. -func (f *Flock) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // if we aren't locked or if the lockfile instance is nil - // just return a nil error because we are unlocked - if (!f.l && !f.r) || f.fh == nil { - return nil - } - - // mark the file as unlocked - if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { - return err - } - - f.fh.Close() - - f.l = false - f.r = false - f.fh = nil - - f.mu.Unlock() - return nil -} - -// TryLock is the preferred function for taking an exclusive file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the exclusive -// file lock, the function will return false instead of waiting for the lock. If -// we get the lock, we also set the *Flock instance as being exclusive-locked. -func (f *Flock) TryLock() (bool, error) { - return f.try(&f.l, syscall.LOCK_EX) -} - -// TryRLock is the preferred function for taking a shared file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the shared file -// lock, the function will return false instead of waiting for the lock. If we -// get the lock, we also set the *Flock instance as being share-locked. -func (f *Flock) TryRLock() (bool, error) { - return f.try(&f.r, syscall.LOCK_SH) -} - -func (f *Flock) try(locked *bool, flag int) (bool, error) { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return true, nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return false, err - } - } - - err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) - - switch err { - case syscall.EWOULDBLOCK: - return false, nil - case nil: - *locked = true - return true, nil - } - - return false, err -} diff --git a/g/os/gflock/internals/flock/flock_winapi.go b/g/os/gflock/internals/flock/flock_winapi.go deleted file mode 100644 index fe405a255..000000000 --- a/g/os/gflock/internals/flock/flock_winapi.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// +build windows - -package flock - -import ( - "syscall" - "unsafe" -) - -var ( - kernel32, _ = syscall.LoadLibrary("kernel32.dll") - procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") - procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") -) - -const ( - winLockfileFailImmediately = 0x00000001 - winLockfileExclusiveLock = 0x00000002 - winLockfileSharedLock = 0x00000000 -) - -// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows -// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: -// -// > The function requests an exclusive lock. Otherwise, it requests a shared -// > lock. -// -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx - -func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { - r1, _, errNo := syscall.Syscall6( - uintptr(procLockFileEx), - 6, - uintptr(handle), - uintptr(flags), - uintptr(reserved), - uintptr(numberOfBytesToLockLow), - uintptr(numberOfBytesToLockHigh), - uintptr(unsafe.Pointer(offset))) - - if r1 != 1 { - if errNo == 0 { - return false, syscall.EINVAL - } - - return false, errNo - } - - return true, 0 -} - -func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { - r1, _, errNo := syscall.Syscall6( - uintptr(procUnlockFileEx), - 5, - uintptr(handle), - uintptr(reserved), - uintptr(numberOfBytesToLockLow), - uintptr(numberOfBytesToLockHigh), - uintptr(unsafe.Pointer(offset)), - 0) - - if r1 != 1 { - if errNo == 0 { - return false, syscall.EINVAL - } - - return false, errNo - } - - return true, 0 -} diff --git a/g/os/gflock/internals/flock/flock_windows.go b/g/os/gflock/internals/flock/flock_windows.go deleted file mode 100644 index a0103f6da..000000000 --- a/g/os/gflock/internals/flock/flock_windows.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -package flock - -import ( - "syscall" -) - -// ErrorLockViolation is the error code returned from the Windows syscall when a -// lock would block and you ask to fail immediately. -const ErrorLockViolation syscall.Errno = 0x21 // 33 - -// Lock is a blocking call to try and take an exclusive file lock. It will wait -// until it is able to obtain the exclusive file lock. It's recommended that -// TryLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) Lock() error { - return f.lock(&f.l, winLockfileExclusiveLock) -} - -// RLock is a blocking call to try and take a sahred file lock. It will wait -// until it is able to obtain the shared file lock. It's recommended that -// TryRLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) RLock() error { - return f.lock(&f.r, winLockfileSharedLock) -} - -func (f *Flock) lock(locked *bool, flag uint32) error { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return err - } - } - - if _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { - return errNo - } - - *locked = true - return nil -} - -// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so -// while it is running the Locked() and RLocked() functions will be blocked. -// -// This function short-circuits if we are unlocked already. If not, it calls -// UnlockFileEx() on the file and closes the file descriptor. It does not remove -// the file from disk. It's up to your application to do. -func (f *Flock) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // if we aren't locked or if the lockfile instance is nil - // just return a nil error because we are unlocked - if (!f.l && !f.r) || f.fh == nil { - return nil - } - - // mark the file as unlocked - if _, errNo := unlockFileEx(syscall.Handle(f.fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { - return errNo - } - - f.fh.Close() - - f.l = false - f.r = false - f.fh = nil - - return nil -} - -// TryLock is the preferred function for taking an exlusive file lock. This -// function does take a RW-mutex lock before it tries to lock the file, so there -// is the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the exclusive -// file lock, the function will return false instead of waiting for the lock. If -// we get the lock, we also set the *Flock instance as being exclusive-locked. -func (f *Flock) TryLock() (bool, error) { - return f.try(&f.l, winLockfileExclusiveLock) -} - -// TryRLock is the preferred function for taking a shared file lock. This -// function does take a RW-mutex lock before it tries to lock the file, so there -// is the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the shared file -// lock, the function will return false instead of waiting for the lock. If we -// get the lock, we also set the *Flock instance as being shared-locked. -func (f *Flock) TryRLock() (bool, error) { - return f.try(&f.r, winLockfileSharedLock) -} - -func (f *Flock) try(locked *bool, flag uint32) (bool, error) { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return true, nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return false, err - } - } - - _, errNo := lockFileEx(syscall.Handle(f.fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) - - if errNo > 0 { - if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { - return false, nil - } - - return false, errNo - } - - *locked = true - - return true, nil -} diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index 2bd5398d8..a062376f7 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -66,8 +66,6 @@ func Remove(path string) error { return watcher.Remove(path) } - - // 创建监听管理对象 func New() (*Watcher, error) { if watch, err := fsnotify.NewWatcher(); err == nil { @@ -155,7 +153,7 @@ func (w *Watcher) startEventLoop() { for { if v := w.events.PopFront(); v != nil { event := v.(*Event) - // 如果是文件删除时间,判断该文件是否存在,如果存在,那么将此事件认为“假删除”,并重新添加监控 + // 如果是文件删除事件,判断该文件是否存在,如果存在,那么将此事件认为“假删除”,并重新添加监控 if event.IsRemove() && gfile.Exists(event.Path){ w.watcher.Add(event.Path) continue diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go index 74735533d..1131b6533 100644 --- a/g/os/gproc/gproc.go +++ b/g/os/gproc/gproc.go @@ -9,74 +9,25 @@ package gproc import ( "os" - "gitee.com/johng/gf/g/container/gmap" - "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/net/gtcp" - "net" + "gitee.com/johng/gf/g/util/gconv" ) const ( - gCOMMUNICATION_MAIN_PORT = 30000 - gCOMMUNICATION_CHILD_PORT = 40000 - gCHILD_PROCESS_ENV_KEY = "gf.process.manager.child" - gCHILD_PROCESS_ENV_STRING = gCHILD_PROCESS_ENV_KEY + "=1" + gPROC_ENV_KEY_PPID_KEY = "gproc.ppid" ) -// TCP通信数据结构定义 -type Msg struct { - Pid int // PID,哪个进程发送的消息 - Data []byte // 参数,消息附带的参数 +// 获取当前进程ID +func Pid() int { + return os.Getpid() } -// 获取其他进程传递到当前进程的消息包,阻塞执行 -func GetMsg() *Msg { - if v := msgQueue.PopFront(); v != nil { - return v.(*Msg) - } - return nil +// 获取父进程ID +func Ppid() int { + return gconv.Int(os.Getenv(gPROC_ENV_KEY_PPID_KEY)) } // 判断当前进程是否为gproc创建的子进程 func IsChild() bool { - return os.Getenv(gCHILD_PROCESS_ENV_KEY) != "" + return os.Getenv(gPROC_ENV_KEY_PPID_KEY) != "" } - -// TCP数据通信处理回调函数 -// 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) -func tcpServiceHandler(conn net.Conn) { - buffer := gtcp.Receive(conn, gtcp.Retry{3, 100}) - msgs := bufferToMsgs(buffer) - if len(msgs) == 0 { - conn.Close() - return - } - for _, msg := range msgs { - msgQueue.PushBack(msg) - } -} - -// 数据解包,防止黏包 -func bufferToMsgs(buffer []byte) []*Msg { - s := 0 - msgs := make([]*Msg, 0) - for s < len(buffer) { - length := gbinary.DecodeToInt(buffer[s : 4]) - if length < 0 || length > len(buffer) { - s++ - continue - } - checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) - checksum2 := gtcp.Checksum(buffer[s + 12 : s + length]) - if checksum1 != checksum2 { - s++ - continue - } - msgs = append(msgs, &Msg { - Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), - Data : buffer[s + 12 : s + length], - }) - s += length - } - return msgs -} diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go new file mode 100644 index 000000000..e948b4cf9 --- /dev/null +++ b/g/os/gproc/gproc_comm.go @@ -0,0 +1,112 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package gproc + +import ( + "os" + "fmt" + "gitee.com/johng/gf/g/os/gfile" + "gitee.com/johng/gf/g/os/gflock" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/os/gfsnotify" + "gitee.com/johng/gf/g/container/gqueue" + "gitee.com/johng/gf/g/encoding/gbinary" +) + +// gproc进程通信共享文件目录地址 +var commDirPath = gfile.TempDir() + gfile.Separator + "gproc" +// 当前进程的文件锁 +var commLocker = gflock.New(fmt.Sprintf("%d.lock", os.Getpid())) +// 进程通信消息队列 +var commQueue = gqueue.New() + +// TCP通信数据结构定义 +type Msg struct { + Pid int // PID,哪个进程发送的消息 + Data []byte // 参数,消息附带的参数 +} + +// 进程管理/通信初始化操作 +func init() { + path := getCommFilePath(os.Getpid()) + if !gfile.Exists(path) { + gfile.Create(path) + } + // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 + gfsnotify.Add(path, func(event *gfsnotify.Event) { + commLocker.Lock() + buffer := gfile.GetBinContents(path) + os.Truncate(path, 0) + commLocker.UnLock() + for _, v := range bufferToMsgs(buffer) { + commQueue.PushBack(v) + } + }) +} + +// 获取其他进程传递到当前进程的消息包,阻塞执行 +func Receive() *Msg { + if v := commQueue.PopFront(); v != nil { + return v.(*Msg) + } + return nil +} + +// 向指定gproc进程发送数据 +// 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) +func Send(pid int, data interface{}) error { + buffer := gconv.Bytes(data) + b := make([]byte, 0) + b = append(b, gbinary.EncodeInt32(int32(len(buffer) + 12))...) + b = append(b, gbinary.EncodeInt32(int32(os.Getpid()))...) + b = append(b, gbinary.EncodeUint32(checksum(buffer))...) + b = append(b, buffer...) + l := gflock.New(fmt.Sprintf("%d.lock", pid)) + l.Lock() + err := gfile.PutBinContentsAppend(getCommFilePath(pid), b) + l.UnLock() + return err +} + +// 获取指定进程的通信文件地址 +func getCommFilePath(pid int) string { + return commDirPath + gfile.Separator + gconv.String(pid) +} + +// 数据解包,防止黏包 +func bufferToMsgs(buffer []byte) []*Msg { + s := 0 + msgs := make([]*Msg, 0) + for s < len(buffer) { + length := gbinary.DecodeToInt(buffer[s : 4]) + if length < 0 || length > len(buffer) { + s++ + continue + } + checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) + checksum2 := checksum(buffer[s + 12 : s + length]) + if checksum1 != checksum2 { + s++ + continue + } + msgs = append(msgs, &Msg { + Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), + Data : buffer[s + 12 : s + length], + }) + s += length + } + return msgs +} + +// 常见的二进制数据校验方式,生成校验结果 +func checksum(buffer []byte) uint32 { + var checksum uint32 + for _, b := range buffer { + checksum += uint32(b) + } + return checksum +} \ No newline at end of file diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index c8ab8b235..f21da9bd1 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -9,13 +9,8 @@ package gproc import ( "os" - "net" - "fmt" - "gitee.com/johng/gf/g/os/glog" - "gitee.com/johng/gf/g/net/gtcp" "gitee.com/johng/gf/g/container/gmap" - "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/container/gqueue" + "fmt" ) // 进程管理器 @@ -23,9 +18,6 @@ type Manager struct { processes *gmap.IntInterfaceMap // 所管理的子进程map } -// 进程通信消息队列 -var msgQueue = gqueue.New() - // 创建一个进程管理器 func New () *Manager { return &Manager{ @@ -33,46 +25,28 @@ func New () *Manager { } } -// 创建主进程与子进程的TCP通信监听服务 -func (m *Manager) startTcpService() { - go func() { - var listen *net.TCPListener - for i := gCOMMUNICATION_MAIN_PORT; i < gCOMMUNICATION_MAIN_PORT + 10000; i++ { - addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("127.0.0.1:%d", i)) - if err != nil { - continue - } - listen, err = net.ListenTCP("tcp", addr) - if err != nil { - continue - } - } - for { - if conn, err := listen.Accept(); err != nil { - glog.Error(err) - } else if conn != nil { - go tcpServiceHandler(conn) - } - } - }() -} - // 创建一个进程(不执行) func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { env := make([]string, len(environment) + 1) for k, v := range environment { env[k] = v } - env[len(env)] = gCHILD_PROCESS_ENV_STRING - return &Process { + env[len(env) - 1] = fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, os.Getpid()) + p := &Process { pm : m, path : path, - args : args, + args : make([]string, 0), attr : &os.ProcAttr { Env : env, Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, }, } + p.args = append(p.args, args[0]) + p.args = append(p.args, "--gproc-child") + if len(args) > 1 { + p.args = append(p.args, args[1:]...) + } + return p } // 获取当前进程管理器中的一个进程 @@ -124,6 +98,11 @@ func (m *Manager) SignalAll(sig os.Signal) error { return nil } +// 获取当前进程管理器中的一个进程 +func (m *Manager) Send(pid int, data interface{}) error { + return Send(pid, data) +} + // 当前进程总数 func (m *Manager) Size() int { return m.processes.Size() diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index b6a85b523..239623dfc 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -8,10 +8,7 @@ package gproc import ( "os" - "fmt" - "gitee.com/johng/gf/g/os/glog" - "net" - "gitee.com/johng/gf/g/net/gtcp" + "errors" ) // 子进程 @@ -37,30 +34,6 @@ func (p *Process) Run() (int, error) { } } -// 创建主进程与子进程的TCP通信监听服务 -func (p *Process) startTcpService() { - go func() { - var listen *net.TCPListener - for i := gCOMMUNICATION_CHILD_PORT; i < gCOMMUNICATION_CHILD_PORT + 10000; i++ { - addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("127.0.0.1:%d", i)) - if err != nil { - continue - } - listen, err = net.ListenTCP("tcp", addr) - if err != nil { - continue - } - } - for { - if conn, err := listen.Accept(); err != nil { - glog.Error(err) - } else if conn != nil { - go tcpServiceHandler(conn) - } - } - }() -} - func (p *Process) SetArgs(args []string) { p.args = args } @@ -97,6 +70,15 @@ func (p *Process) Pid() int { return 0 } +// 向进程发送消息 +func (p *Process) Send(data interface{}) error { + if p.process != nil { + return Send(p.process.Pid, data) + } + return errors.New("process not running") +} + + // Release releases any resources associated with the Process p, // rendering it unusable in the future. // Release only needs to be called if Wait is not. diff --git a/geg/os/gproc/gproc.go b/geg/os/gproc/gproc.go new file mode 100644 index 000000000..73c4f6896 --- /dev/null +++ b/geg/os/gproc/gproc.go @@ -0,0 +1,27 @@ +package main + +import ( + "os" + "fmt" + "time" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/os/gproc" +) + +func main () { + if gproc.IsChild() { + gtime.SetInterval(3*time.Second, func() bool { + gproc.Send(gproc.Ppid(), gtime.Datetime()) + return true + }) + select { } + } else { + m := gproc.New() + p := m.NewProcess(os.Args[0], os.Args, nil) + p.Run() + for { + msg := gproc.Receive() + fmt.Printf("pid is %d, receive from %d: %s\n", os.Getpid(), msg.Pid, string(msg.Data)) + } + } +} diff --git a/geg/os/gproc/gproc2.go b/geg/os/gproc/gproc2.go new file mode 100644 index 000000000..11ee52b4b --- /dev/null +++ b/geg/os/gproc/gproc2.go @@ -0,0 +1,11 @@ +package main + +import ( + "gitee.com/johng/gf/g/os/gproc" + "fmt" +) + +func main () { + err := gproc.Send(11177, "hello process!") + fmt.Println(err) +} From d97e8c820425e9e107a717a8464485e8c4dd2abd Mon Sep 17 00:00:00 2001 From: John Date: Thu, 10 May 2018 17:48:47 +0800 Subject: [PATCH 08/31] =?UTF-8?q?=E5=AE=8C=E6=88=90gproc=E5=8C=85=E7=9A=84?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 4 +-- g/os/gflock/gflock.go | 10 ++++++- g/os/gfsnotify/gfsnotify.go | 4 +-- g/os/gproc/gproc_comm.go | 21 ++++++++++---- g/os/gproc/gproc_manager.go | 5 ++-- geg/os/gflock/flock.go | 17 +++++++++++ geg/os/gflock/gflock.go | 26 ++++++----------- geg/os/gfsnotify/gfsnotify.go | 2 +- geg/os/gproc/gproc.go | 8 ++++-- geg/os/gproc/gproc2.go | 4 +-- geg/other/test.go | 53 +++++++++++++++++++++++++++-------- 11 files changed, 108 insertions(+), 46 deletions(-) create mode 100644 geg/os/gflock/flock.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index d0216ec09..21e96adc0 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -26,6 +26,7 @@ import ( "syscall" "time" "gitee.com/johng/gf/g/os/gcmd" + "gitee.com/johng/gf/g/os/gproc" ) const ( @@ -189,8 +190,7 @@ func (s *Server) startServer() { time.Sleep(1000*time.Second) return } - // 信号量控制监听 - go s.handleSignals() + // 开始执行底层Web Server创建,端口监听 var fd = 3 var wg sync.WaitGroup diff --git a/g/os/gflock/gflock.go b/g/os/gflock/gflock.go index b59718b4b..c8d10a849 100644 --- a/g/os/gflock/gflock.go +++ b/g/os/gflock/gflock.go @@ -21,13 +21,21 @@ type Locker struct { // 创建文件锁 func New(file string) *Locker { - path := gfile.TempDir() + gfile.Separator + "gflock" + gfile.Separator + file + dir := gfile.TempDir() + gfile.Separator + "gflock" + if !gfile.Exists(dir) { + gfile.Mkdir(dir) + } + path := dir + gfile.Separator + file lock := flock.NewFlock(path) return &Locker{ flock : lock, } } +func (l *Locker) Path() string { + return l.flock.Path() +} + func (l *Locker) Lock() { l.mu.Lock() l.flock.Lock() diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index a062376f7..c032f5e2c 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -25,7 +25,7 @@ import ( type Watcher struct { watcher *fsnotify.Watcher // 底层fsnotify对象 events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 - eventCache *gcache.Cache // 用于进行事件过滤,当同一监听文件在100ms内出现相同事件,则过滤 + eventCache *gcache.Cache // 用于进行事件过滤,当同一监听文件在10ms内出现相同事件,则过滤 closeChan chan struct{} // 关闭事件 callbacks *gmap.StringInterfaceMap // 监听的回调函数 } @@ -132,7 +132,7 @@ func (w *Watcher) startWatchLoop() { // 监听事件 case ev := <- w.watcher.Events: - if !w.eventCache.Lock(ev.Name + ":" + gconv.String(ev.Op), 100) { + if !w.eventCache.Lock(ev.Name + ":" + gconv.String(ev.Op), 10) { continue } w.events.PushBack(&Event{ diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index e948b4cf9..70a42cd6f 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -9,6 +9,7 @@ package gproc import ( "os" "fmt" + "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gflock" "gitee.com/johng/gf/g/util/gconv" @@ -17,8 +18,11 @@ import ( "gitee.com/johng/gf/g/encoding/gbinary" ) -// gproc进程通信共享文件目录地址 -var commDirPath = gfile.TempDir() + gfile.Separator + "gproc" +const ( + // 由于子进程的temp dir有可能会和父进程不一致,影响进程间通信,这里统一使用环境变量设置 + gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" +) + // 当前进程的文件锁 var commLocker = gflock.New(fmt.Sprintf("%d.lock", os.Getpid())) // 进程通信消息队列 @@ -37,7 +41,7 @@ func init() { gfile.Create(path) } // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 - gfsnotify.Add(path, func(event *gfsnotify.Event) { + err := gfsnotify.Add(path, func(event *gfsnotify.Event) { commLocker.Lock() buffer := gfile.GetBinContents(path) os.Truncate(path, 0) @@ -46,6 +50,9 @@ func init() { commQueue.PushBack(v) } }) + if err != nil { + glog.Error(err) + } } // 获取其他进程传递到当前进程的消息包,阻塞执行 @@ -74,7 +81,11 @@ func Send(pid int, data interface{}) error { // 获取指定进程的通信文件地址 func getCommFilePath(pid int) string { - return commDirPath + gfile.Separator + gconv.String(pid) + tempDir := os.Getenv("gproc.tempdir") + if tempDir == "" { + tempDir = gfile.TempDir() + } + return tempDir + gfile.Separator + "gproc" + gfile.Separator + gconv.String(pid) } // 数据解包,防止黏包 @@ -82,7 +93,7 @@ func bufferToMsgs(buffer []byte) []*Msg { s := 0 msgs := make([]*Msg, 0) for s < len(buffer) { - length := gbinary.DecodeToInt(buffer[s : 4]) + length := gbinary.DecodeToInt(buffer[s : s + 4]) if length < 0 || length > len(buffer) { s++ continue diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index f21da9bd1..18b397b4b 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -27,11 +27,12 @@ func New () *Manager { // 创建一个进程(不执行) func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { - env := make([]string, len(environment) + 1) + env := make([]string, len(environment) + 2) for k, v := range environment { env[k] = v } - env[len(env) - 1] = fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, os.Getpid()) + env[len(env) - 2] = fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, os.Getpid()) + env[len(env) - 1] = fmt.Sprintf("%s=%s", gPROC_TEMP_DIR_ENV_KEY, os.TempDir()) p := &Process { pm : m, path : path, diff --git a/geg/os/gflock/flock.go b/geg/os/gflock/flock.go new file mode 100644 index 000000000..cb02bfd7b --- /dev/null +++ b/geg/os/gflock/flock.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "github.com/theckman/go-flock" + "time" +) + +func main() { + l := flock.NewFlock("/tmp/go-lock.lock") + l.Lock() + fmt.Printf("lock 1") + l.Lock() + fmt.Printf("lock 1") + + time.Sleep(time.Hour) +} diff --git a/geg/os/gflock/gflock.go b/geg/os/gflock/gflock.go index 2fc22fc97..b2ad294d7 100644 --- a/geg/os/gflock/gflock.go +++ b/geg/os/gflock/gflock.go @@ -1,25 +1,17 @@ package main import ( - "github.com/theckman/go-flock" + "gitee.com/johng/gf/g/os/gflock" "fmt" + "time" ) func main() { - fileLock := flock.NewFlock("/var/lock/go-lock.lock") - - fmt.Println(fileLock.TryLock()) - fmt.Println(fileLock.TryRLock()) - //time.Sleep(1000*time.Second) -//fmt.Println(locked) -// fmt.Println(fileLock.Locked()) -//fmt.Println(err) -// if err != nil { -// // handle locking error -// } -// -// if locked { -// // do work -// fileLock.Unlock() -// } + l := gflock.New("1.lock") + fmt.Println(l.Path()) + fmt.Println(l.Lock()) + fmt.Println("lock 1") + fmt.Println(l.Lock()) + fmt.Println("lock 1") + time.Sleep(time.Hour) } diff --git a/geg/os/gfsnotify/gfsnotify.go b/geg/os/gfsnotify/gfsnotify.go index 0ab4144e7..023411a6b 100644 --- a/geg/os/gfsnotify/gfsnotify.go +++ b/geg/os/gfsnotify/gfsnotify.go @@ -6,7 +6,7 @@ import ( ) func main() { - err := gfsnotify.Add("/home/john/Documents/temp.txt", func(event *gfsnotify.Event) { + err := gfsnotify.Add("./temp.txt", func(event *gfsnotify.Event) { if event.IsCreate() { log.Println("创建文件 : ", event.Path) } diff --git a/geg/os/gproc/gproc.go b/geg/os/gproc/gproc.go index 73c4f6896..85332d7ae 100644 --- a/geg/os/gproc/gproc.go +++ b/geg/os/gproc/gproc.go @@ -4,24 +4,26 @@ import ( "os" "fmt" "time" - "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/os/gtime" ) func main () { if gproc.IsChild() { - gtime.SetInterval(3*time.Second, func() bool { + fmt.Printf(" child pid is %d\n", os.Getpid()) + gtime.SetInterval(time.Second, func() bool { gproc.Send(gproc.Ppid(), gtime.Datetime()) return true }) select { } } else { + fmt.Printf("parent pid is %d\n", os.Getpid()) m := gproc.New() p := m.NewProcess(os.Args[0], os.Args, nil) p.Run() for { msg := gproc.Receive() - fmt.Printf("pid is %d, receive from %d: %s\n", os.Getpid(), msg.Pid, string(msg.Data)) + fmt.Printf("receive from %d, data: %s\n", msg.Pid, string(msg.Data)) } } } diff --git a/geg/os/gproc/gproc2.go b/geg/os/gproc/gproc2.go index 11ee52b4b..a0a3f0b56 100644 --- a/geg/os/gproc/gproc2.go +++ b/geg/os/gproc/gproc2.go @@ -1,11 +1,11 @@ package main import ( - "gitee.com/johng/gf/g/os/gproc" "fmt" + "gitee.com/johng/gf/g/os/gproc" ) func main () { - err := gproc.Send(11177, "hello process!") + err := gproc.Send(29260, "hello process!") fmt.Println(err) } diff --git a/geg/other/test.go b/geg/other/test.go index e57a6b864..9a6703bc3 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -3,11 +3,9 @@ package main import ( "fmt" "net/http" - "github.com/tabalt/gracehttp" - "os" - "gitee.com/johng/gf/g/os/gpm" - "time" + "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/os/gproc" ) @@ -22,11 +20,44 @@ func test() { } } -func main() { - m := gproc.New() - args := os.Args - args = append(args, "--child=1") - p := m.NewProcess(args[0], args, nil) - p.Run() - time.Sleep(100*time.Second) +// 常见的二进制数据校验方式,生成校验结果 +func checksum(buffer []byte) uint32 { + var checksum uint32 + for _, b := range buffer { + checksum += uint32(b) + } + return checksum +} + +// 数据解包,防止黏包 +func bufferToMsgs(buffer []byte) []*gproc.Msg { + s := 0 + msgs := make([]*gproc.Msg, 0) + for s < len(buffer) { + fmt.Println(s) + length := gbinary.DecodeToInt(buffer[s : s + 4]) + if length < 0 || length > len(buffer) { + s++ + continue + } + checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) + checksum2 := checksum(buffer[s + 12 : s + length]) + if checksum1 != checksum2 { + s++ + continue + } + msgs = append(msgs, &gproc.Msg { + Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), + Data : buffer[s + 12 : s + length], + }) + s += length + } + return msgs +} + + +func main() { + b := []byte{26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33} + m := bufferToMsgs(b) + fmt.Println(m) } From 806d565a32ee10d50a475a9e159539b2fc666116 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 10 May 2018 19:16:41 +0800 Subject: [PATCH 09/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 135 ++++++++++++++++----------- g/net/ghttp/ghttp_server_cmd.go | 26 ------ g/net/ghttp/ghttp_server_comm.go | 59 ++++++++++++ g/net/ghttp/ghttp_server_graceful.go | 7 ++ g/os/gproc/gproc_comm.go | 2 + g/os/gproc/gproc_manager.go | 28 ++++-- g/os/gproc/gproc_proccess.go | 20 +++- 7 files changed, 190 insertions(+), 87 deletions(-) delete mode 100644 g/net/ghttp/ghttp_server_cmd.go create mode 100644 g/net/ghttp/ghttp_server_comm.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 21e96adc0..dff88d624 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -20,13 +20,13 @@ import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" "fmt" - "gitee.com/johng/gf/g/os/gpm" "net" - "os/signal" "syscall" - "time" "gitee.com/johng/gf/g/os/gcmd" "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/os/gtime" + "time" ) const ( @@ -83,7 +83,6 @@ type Server struct { errorLogger *glog.Logger // error log日志对象 // 多进程管理控制 manager *gproc.Manager // 多进程管理 - heartbeats } // 域名、URI与回调函数的绑定记录表 @@ -146,8 +145,7 @@ func GetServer(name...interface{}) (*Server) { accessLogger : glog.New(), errorLogger : glog.New(), logHandler : gtype.NewInterface(), - manager : gproc.New(), - signalChan : make(chan os.Signal), + manager : gproc.NewManager(), } s.errorLogger.SetBacktraceSkip(4) s.accessLogger.SetBacktraceSkip(4) @@ -176,25 +174,26 @@ func (s *Server) Run() error { // 开启异步关闭队列处理循环 s.startCloseQueueLoop() - // 开启Web Server执行 - s.startServer() + + // 主进程只负责创建子进程 + if !gproc.IsChild() { + p := s.manager.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Run() + gtime.SetTimeout(3*time.Second, func() { + b, _ := gjson.Encode(s.getAllListenerFdMap()) + s.sendMsg(p.Pid(), gMSG_START, b) + }) + } + // 开启进程消息监听处理 + s.handleProcessMsg() return nil } // 开启底层Web Server执行 -func (s *Server) startServer() { - // 主进程只负责创建子进程 - if !s.isChildProcess() { - s.forkChildProcess() - time.Sleep(10*time.Second) - time.Sleep(1000*time.Second) - return - } - +func (s *Server) startServer(fdMap map[string]string) { + fmt.Println("startServer") // 开始执行底层Web Server创建,端口监听 - var fd = 3 var wg sync.WaitGroup - var fcount = s.processFileCount() var server *gracefulServer if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { // HTTPS @@ -205,15 +204,23 @@ func (s *Server) startServer() { s.config.HTTPSAddr = gDEFAULT_HTTPS_ADDR } } - array := strings.Split(s.config.HTTPSAddr, ",") + var array []string + var isFd bool + if v, ok := fdMap["https"]; ok && len(v) > 0 { + isFd = true + array = strings.Split(v, ",") + } else { + array = strings.Split(s.config.HTTPSAddr, ",") + } + for _, v := range array { wg.Add(1) - go func(addr string) { - if s.isChildProcess() && fcount > 0 { - server = s.newGracefulServer(addr, fd) - fd++ + go func(item string) { + if isFd { + tArray := strings.Split(item, ":") + server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { - server = s.newGracefulServer(addr) + server = s.newGracefulServer(item) } s.servers = append(s.servers, server) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { @@ -230,22 +237,27 @@ func (s *Server) startServer() { if s.servedCount.Val() == 0 && len(s.config.Addr) == 0 { s.config.Addr = gDEFAULT_HTTP_ADDR } - array := strings.Split(s.config.Addr, ",") + var array []string + var isFd bool + if v, ok := fdMap["http"]; ok && len(v) > 0 { + isFd = true + array = strings.Split(v, ",") + } else { + array = strings.Split(s.config.Addr, ",") + } for _, v := range array { wg.Add(1) - go func(addr string) { - if s.isChildProcess() && fcount > 0 { - server = s.newGracefulServer(addr, fd) - fd++ + go func(item string) { + if isFd { + tArray := strings.Split(item, ":") + server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { - server = s.newGracefulServer(addr) + server = s.newGracefulServer(item) } s.servers = append(s.servers, server) if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { - glog.Println(fd) - glog.Println(os.Args) glog.Error(err) } wg.Done() @@ -259,26 +271,6 @@ func (s *Server) startServer() { wg.Wait() } -// 异步处理信号量监控 -func (s *Server) handleSignals() { - var sig os.Signal - - signal.Notify( - s.signalChan, - syscall.SIGTERM, - syscall.SIGUSR2, - ) - - for { - sig = <- s.signalChan - switch sig { - case syscall.SIGTERM: s.Shutdown() - case syscall.SIGUSR2: s.Restart() - default: - } - } -} - // 重启Web Server func (s *Server) Restart() { // 如果是主进程,那么向所有子进程发送重启信号 @@ -325,6 +317,43 @@ func (s *Server) getTopId() int { return 0 } +// 获取当前监听的文件描述符信息,构造成map返回 +func (s *Server) getAllListenerFdMap() map[string]string { + m := map[string]string{ + "http" : "", + "https" : "", + } + for _, v := range s.servers { + if f, e := v.listener.(*net.TCPListener).File(); e == nil { + str := v.addr + ":" + gconv.String(f.Fd()) + "," + if v.isHttps { + m["https"] += str + } else { + m["http"] += str + } + } else { + glog.Errorfln("failed to get listener file: %v", e) + } + } + if len(m["http"]) > 0 { + m["http"] = m["http"][0 : len(m["http"]) - 1] + } + if len(m["https"]) > 0 { + m["https"] = m["https"][0 : len(m["https"]) - 1] + } + return m +} + +// 二进制转换为FdMap +func (s *Server) bufferToFdMap(buffer []byte) map[string]string { + m := make(map[string]string) + j, _ := gjson.LoadContent(buffer, "json") + for k, v := range j.ToMap() { + m[k] = gconv.String(v) + } + return m +} + // 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 func (s *Server) forkChildProcess() (int, error) { // 获取所有http server的file diff --git a/g/net/ghttp/ghttp_server_cmd.go b/g/net/ghttp/ghttp_server_cmd.go deleted file mode 100644 index d975fc7a2..000000000 --- a/g/net/ghttp/ghttp_server_cmd.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. -// -// 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://gitee.com/johng/gf. - -package ghttp - -import ( - "fmt" - "gitee.com/johng/gf/g/net/gtcp" -) - -// 开启命令监听端口 -func (s *Server) startCmdService() { - s.BindHandler("/heartbeat", func(r *Request) { - - }) - s.BindHandler("/restart", func(r *Request) { - - }) - server := s.newGracefulServer(fmt.Sprintf("127.0.0.1:%d", s.cmdPort)) - if err := server.ListenAndServe(); err != nil { - - } -} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go new file mode 100644 index 000000000..e64c471c2 --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm.go @@ -0,0 +1,59 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. +// Web Server进程间通信 + +package ghttp + +import ( + "os" + "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/encoding/gbinary" + "fmt" +) + +const ( + gMSG_START = iota + gMSG_RESTART + gMSG_SHUTDOWN + gMSG_EXIT +) + + +// 处理进程间消息 +// 数据格式: 操作(8bit) | 参数(变长) +func (s *Server) handleProcessMsg() { + for { + if msg := gproc.Receive(); msg != nil { + fmt.Println(msg) + act := gbinary.DecodeToInt(msg.Data[0 : 1]) + data := msg.Data[1 : ] + if gproc.IsChild() { + switch act { + case gMSG_START: + s.startServer(s.bufferToFdMap(data)) + case gMSG_RESTART: + case gMSG_SHUTDOWN: s.Shutdown() + case gMSG_EXIT: os.Exit(0) + + } + } else { + switch act { + case gMSG_START: + case gMSG_RESTART: + case gMSG_SHUTDOWN: + case gMSG_EXIT: os.Exit(0) + + } + } + } + } +} + +// 向进程发送操作消息 +func (s *Server) sendMsg(pid int, act int, data []byte) { + gproc.Send(pid, append(gbinary.EncodeInt8(int8(act)), data...)) +} + diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 70022128a..29688a5cc 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -22,6 +22,7 @@ type gracefulServer struct { addr string httpServer *http.Server listener net.Listener + isHttps bool shutdownChan chan bool } @@ -61,6 +62,11 @@ func (s *gracefulServer) ListenAndServe() error { return s.doServe() } +// 设置自定义fd +func (s *gracefulServer) setFd(fd int) { + s.fd = uintptr(fd) +} + // 执行HTTPS监听 func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { addr := s.httpServer.Addr @@ -82,6 +88,7 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { return err } s.listener = tls.NewListener(ln, config) + s.isHttps = true return s.doServe() } diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 70a42cd6f..13dffa35d 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -40,8 +40,10 @@ func init() { if !gfile.Exists(path) { gfile.Create(path) } + fmt.Println(path) // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 err := gfsnotify.Add(path, func(event *gfsnotify.Event) { + fmt.Println(event) commLocker.Lock() buffer := gfile.GetBinContents(path) os.Truncate(path, 0) diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index 18b397b4b..336c2a931 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -19,24 +19,23 @@ type Manager struct { } // 创建一个进程管理器 -func New () *Manager { +func NewManager() *Manager { return &Manager{ processes : gmap.NewIntInterfaceMap(), } } // 创建一个进程(不执行) -func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { +func NewProcess(path string, args []string, environment []string) *Process { env := make([]string, len(environment) + 2) for k, v := range environment { env[k] = v } - env[len(env) - 2] = fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, os.Getpid()) env[len(env) - 1] = fmt.Sprintf("%s=%s", gPROC_TEMP_DIR_ENV_KEY, os.TempDir()) p := &Process { - pm : m, path : path, args : make([]string, 0), + ppid : os.Getppid(), attr : &os.ProcAttr { Env : env, Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, @@ -50,6 +49,13 @@ func (m *Manager) NewProcess(path string, args []string, environment []string) * return p } +// 创建一个进程(不执行) +func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { + p := NewProcess(path, args, environment) + p.SetManager(m) + return p +} + // 获取当前进程管理器中的一个进程 func (m *Manager) GetProcess(pid int) *Process { if v := m.processes.Get(pid); v != nil { @@ -99,8 +105,18 @@ func (m *Manager) SignalAll(sig os.Signal) error { return nil } -// 获取当前进程管理器中的一个进程 -func (m *Manager) Send(pid int, data interface{}) error { +// 向所有进程发送消息 +func (m *Manager) Send(data interface{}) error { + for _, p := range m.Processes() { + if err := p.Send(data); err != nil { + return err + } + } + return nil +} + +// 向指定进程发送消息 +func (m *Manager) SendTo(pid int, data interface{}) error { return Send(pid, data) } diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index 239623dfc..0420d1a17 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -9,6 +9,7 @@ package gproc import ( "os" "errors" + "fmt" ) // 子进程 @@ -17,6 +18,7 @@ type Process struct { path string // 可执行文件绝对路径 args []string // 执行参数 attr *os.ProcAttr // 进程属性 + ppid int // 自定义关联的父进程ID process *os.Process // 底层进程对象 } @@ -25,15 +27,27 @@ func (p *Process) Run() (int, error) { if p.process != nil { return p.Pid(), nil } + p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { p.process = process - p.pm.processes.Set(process.Pid, p) + if p.pm != nil { + p.pm.processes.Set(process.Pid, p) + } return process.Pid, nil } else { return 0, err } } +func (p *Process) SetManager(m *Manager) { + p.pm = m +} + +// 设置自定义的父进程ID +func (p *Process) SetPpid(ppid int) { + p.ppid = ppid +} + func (p *Process) SetArgs(args []string) { p.args = args } @@ -89,7 +103,9 @@ func (p *Process) Release() error { // Kill causes the Process to exit immediately. func (p *Process) Kill() error { if err := p.process.Kill(); err == nil { - p.pm.processes.Remove(p.Pid()) + if p.pm != nil { + p.pm.processes.Remove(p.Pid()) + } return nil } else { return err From b1e3ddce2f2a9dd40704f09d9e9b0280bb243285 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 10 May 2018 23:52:09 +0800 Subject: [PATCH 10/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 167 ++++++++++++++----------------- g/net/ghttp/ghttp_server_comm.go | 52 ++++++++-- g/os/gfile/gfile.go | 4 + g/os/gproc/gproc_comm.go | 35 +++++-- geg/other/test.go | 64 ++---------- 5 files changed, 154 insertions(+), 168 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index dff88d624..72e742b34 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -19,14 +19,9 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" - "fmt" "net" - "syscall" - "gitee.com/johng/gf/g/os/gcmd" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/encoding/gjson" - "gitee.com/johng/gf/g/os/gtime" - "time" ) const ( @@ -38,7 +33,6 @@ const ( gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年) gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒) gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称 - gDEFAULT_COMMAND_PORT = 336816 // 默认本地命令控制端口 ) // ghttp.Server结构体 @@ -48,7 +42,6 @@ type Server struct { config ServerConfig // 配置对象 status int8 // 当前服务器状态(0:未启动,1:运行中) servers []*gracefulServer // 底层http.Server列表 - cmdPort int // 本地Web Server命令控制端口 methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充) servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象) @@ -81,8 +74,6 @@ type Server struct { accessLogEnabled *gtype.Bool // 是否开启access log accessLogger *glog.Logger // access log日志对象 errorLogger *glog.Logger // error log日志对象 - // 多进程管理控制 - manager *gproc.Manager // 多进程管理 } // 域名、URI与回调函数的绑定记录表 @@ -107,9 +98,61 @@ type HandlerItem struct { // http注册函数 type HandlerFunc func(r *Request) +// 文件描述符map +type listenerFdMap map[string]string + // Server表,用以存储和检索名称与Server对象之间的关联关系 var serverMapping = gmap.NewStringInterfaceMap() +// Web Server多进程管理器 +var procManager = gproc.NewManager() + +// Web Server开始执行事件通道,由于同一个进程支持多Server,因此该通道为非阻塞 +var runChan = make(chan struct{}, 100000) +// 已完成的run消息队列 +var doneChan = make(chan struct{}, 100000) + +// Web Server进程初始化 +func init() { + go func() { + <- runChan + // 主进程只负责创建子进程 + if !gproc.IsChild() { + sendProcessMsg(os.Getpid(), gMSG_START, nil) + } + // 开启进程消息监听处理 + handleProcessMsg() + doneChan <- struct{}{} + }() +} + +// 获取所有Web Server的文件描述符map +func getServerFdMap() map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + serverMapping.RLockFunc(func(m map[string]interface{}) { + for k, v := range m { + sfm[k] = v.(*Server).getListenerFdMap() + } + }) + return sfm +} + +// 二进制转换为FdMap +func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + if len(buffer) > 0 { + j, _ := gjson.LoadContent(buffer, "json") + for k, _ := range j.ToMap() { + m := make(map[string]string) + for k, v := range j.GetMap(k) { + m[k] = gconv.String(v) + } + sfm[k] = m + } + } + return sfm +} + // 获取/创建一个默认配置的HTTP Server(默认监听端口是80) // 单例模式,请保证name的唯一性 func GetServer(name...interface{}) (*Server) { @@ -123,7 +166,6 @@ func GetServer(name...interface{}) (*Server) { s := &Server { name : sname, servers : make([]*gracefulServer, 0), - cmdPort : gDEFAULT_COMMAND_PORT, methodsMap : make(map[string]bool), handlerMap : make(HandlerMap), statusHandlerMap : make(map[string]HandlerFunc), @@ -145,7 +187,6 @@ func GetServer(name...interface{}) (*Server) { accessLogger : glog.New(), errorLogger : glog.New(), logHandler : gtype.NewInterface(), - manager : gproc.NewManager(), } s.errorLogger.SetBacktraceSkip(4) s.accessLogger.SetBacktraceSkip(4) @@ -162,6 +203,13 @@ func GetServer(name...interface{}) (*Server) { // 阻塞执行监听 func (s *Server) Run() error { + runChan <- struct{}{} + + if !gproc.IsChild() { + <- doneChan + return nil + } + if s.status == 1 { return errors.New("server is already running") } @@ -174,24 +222,12 @@ func (s *Server) Run() error { // 开启异步关闭队列处理循环 s.startCloseQueueLoop() - - // 主进程只负责创建子进程 - if !gproc.IsChild() { - p := s.manager.NewProcess(os.Args[0], os.Args, os.Environ()) - p.Run() - gtime.SetTimeout(3*time.Second, func() { - b, _ := gjson.Encode(s.getAllListenerFdMap()) - s.sendMsg(p.Pid(), gMSG_START, b) - }) - } - // 开启进程消息监听处理 - s.handleProcessMsg() + <- doneChan return nil } // 开启底层Web Server执行 -func (s *Server) startServer(fdMap map[string]string) { - fmt.Println("startServer") +func (s *Server) startServer(fdMap listenerFdMap) { // 开始执行底层Web Server创建,端口监听 var wg sync.WaitGroup var server *gracefulServer @@ -223,6 +259,7 @@ func (s *Server) startServer(fdMap map[string]string) { server = s.newGracefulServer(item) } s.servers = append(s.servers, server) + glog.Printfln("https server started listening on %s", server.addr) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { @@ -255,6 +292,7 @@ func (s *Server) startServer(fdMap map[string]string) { server = s.newGracefulServer(item) } s.servers = append(s.servers, server) + glog.Printfln("http server started listening on %s", server.addr) if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { @@ -274,23 +312,23 @@ func (s *Server) startServer(fdMap map[string]string) { // 重启Web Server func (s *Server) Restart() { // 如果是主进程,那么向所有子进程发送重启信号 - if !s.isChildProcess() { - s.manager.SignalAll(syscall.SIGUSR2) + if !gproc.IsChild() { + return } - if pid, err := s.forkChildProcess(); err != nil { - glog.Errorf("server restart failed: %v, continue serving\n", err) - } else { - glog.Printf("server restart successfully, new pid: %d\n", pid) - s.Shutdown() - } + //if pid, err := s.forkChildProcess(); err != nil { + // glog.Errorf("server restart failed: %v, continue serving\n", err) + //} else { + // glog.Printf("server restart successfully, new pid: %d\n", pid) + // s.Shutdown() + //} } // 关闭Web Server func (s *Server) Shutdown() { // 如果是主进程,那么向所有子进程发送关闭信号 - if !s.isChildProcess() { - s.manager.SignalAll(syscall.SIGTERM) + if !gproc.IsChild() { + return } for _, v := range s.servers { @@ -298,27 +336,10 @@ func (s *Server) Shutdown() { } } -// 子进程获取的文件打开数 -func (s *Server) processFileCount() int { - return gconv.Int(gcmd.Option.Get("fcount")) -} -// 判断是否为子进程执行 -func (s *Server) isChildProcess() bool { - return s.getTopId() > 0 -} - -// 获取顶级进程ID(管理进程ID) -func (s *Server) getTopId() int { - id := gcmd.Option.Get("topid") - if id != "" { - return gconv.Int(id) - } - return 0 -} // 获取当前监听的文件描述符信息,构造成map返回 -func (s *Server) getAllListenerFdMap() map[string]string { +func (s *Server) getListenerFdMap() map[string]string { m := map[string]string{ "http" : "", "https" : "", @@ -344,46 +365,6 @@ func (s *Server) getAllListenerFdMap() map[string]string { return m } -// 二进制转换为FdMap -func (s *Server) bufferToFdMap(buffer []byte) map[string]string { - m := make(map[string]string) - j, _ := gjson.LoadContent(buffer, "json") - for k, v := range j.ToMap() { - m[k] = gconv.String(v) - } - return m -} - -// 创建子进程来监听并处理新的HTTP请求,与父进程使用的是同一个socket文件描述符 -func (s *Server) forkChildProcess() (int, error) { - // 获取所有http server的file - files := []*os.File{os.Stdin,os.Stdout,os.Stderr} - for _, v := range s.servers { - if f, e := v.listener.(*net.TCPListener).File(); e == nil { - files = append(files, f) - } else { - return 0, fmt.Errorf("failed to get listener file: %v", e) - } - } - // 开启子进程,并传递socket文件指针 - topId := s.getTopId() - if topId == 0 { - topId = os.Getpid() - } - args := make([]string, 4) - args[0] = os.Args[0] - args[1] = fmt.Sprintf("--name=%s", s.name) - args[2] = fmt.Sprintf("--port=%d", s.cmdPort) - args[3] = fmt.Sprintf("--fcount=%d", len(files) - 3) - p := s.manager.NewProcess(os.Args[0], args, os.Environ()) - p.GetAttr().Files = files - if pid, err := p.Run(); err != nil { - return 0, fmt.Errorf("failed to fork process: %v", err) - } else { - return pid, nil - } -} - // 清空当前的handlerCache func (s *Server) clearHandlerCache() { s.hmcmu.Lock() diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index e64c471c2..7dca87c9c 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -8,23 +8,23 @@ package ghttp import ( - "os" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/encoding/gbinary" "fmt" + "gitee.com/johng/gf/g/encoding/gjson" + "os" ) const ( gMSG_START = iota gMSG_RESTART gMSG_SHUTDOWN - gMSG_EXIT ) // 处理进程间消息 // 数据格式: 操作(8bit) | 参数(变长) -func (s *Server) handleProcessMsg() { +func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { fmt.Println(msg) @@ -32,19 +32,48 @@ func (s *Server) handleProcessMsg() { data := msg.Data[1 : ] if gproc.IsChild() { switch act { + // 开启所有Web Server(根据消息启动) case gMSG_START: - s.startServer(s.bufferToFdMap(data)) + sfm := bufferToServerFdMap(data) + for k, v := range sfm { + GetServer(k).startServer(v) + } + + // 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 case gMSG_RESTART: - case gMSG_SHUTDOWN: s.Shutdown() - case gMSG_EXIT: os.Exit(0) + b, _ := gjson.Encode(getServerFdMap()) + sendProcessMsg(gproc.Ppid(), gMSG_RESTART, b) + + // 友好关闭服务链接并退出 + case gMSG_SHUTDOWN: + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Server).Shutdown() + } + }) + return } } else { switch act { + // 开启服务 case gMSG_START: + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Run() + sendProcessMsg(p.Pid(), gMSG_START, nil) + + // 重启服务 case gMSG_RESTART: + // 创建新的服务进程,使用文件描述来监听同样的端口 + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Run() + sendProcessMsg(p.Pid(), gMSG_START, data) + // 关闭旧的服务进程 + sendProcessMsg(msg.Pid, gMSG_SHUTDOWN, nil) + + // 关闭服务 case gMSG_SHUTDOWN: - case gMSG_EXIT: os.Exit(0) + procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) } } @@ -53,7 +82,12 @@ func (s *Server) handleProcessMsg() { } // 向进程发送操作消息 -func (s *Server) sendMsg(pid int, act int, data []byte) { - gproc.Send(pid, append(gbinary.EncodeInt8(int8(act)), data...)) +func sendProcessMsg(pid int, act int, data []byte) { + gproc.Send(pid, formatMsgBuffer(act, data)) +} + +// 生成一条满足Web Server进程通信协议的消息 +func formatMsgBuffer(act int, data []byte) []byte { + return append(gbinary.EncodeInt8(int8(act)), data...) } diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 1efde864e..2f4176039 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -46,6 +46,10 @@ func Mkdir(path string) error { // 给定文件的绝对路径创建文件 func Create(path string) error { + dir := Dir(path) + if !Exists(dir) { + Mkdir(dir) + } f, err := os.Create(path) if err != nil { return err diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 13dffa35d..de000c747 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -16,6 +16,7 @@ import ( "gitee.com/johng/gf/g/os/gfsnotify" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/os/gtime" ) const ( @@ -38,25 +39,39 @@ type Msg struct { func init() { path := getCommFilePath(os.Getpid()) if !gfile.Exists(path) { - gfile.Create(path) + if err := gfile.Create(path); err != nil { + glog.Error(err) + } + } else { + // 初始化时读取已有数据(文件修改时间在10秒以内) + if gtime.Second() - gfile.MTime(path) < 10 { + checkCommBuffer(path) + } } - fmt.Println(path) // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 err := gfsnotify.Add(path, func(event *gfsnotify.Event) { - fmt.Println(event) - commLocker.Lock() - buffer := gfile.GetBinContents(path) - os.Truncate(path, 0) - commLocker.UnLock() - for _, v := range bufferToMsgs(buffer) { - commQueue.PushBack(v) - } + checkCommBuffer(path) }) if err != nil { glog.Error(err) } } +// 手动检查进程通信消息,如果存在消息曾推送到进程消息队列 +func checkCommBuffer(path string) { + commLocker.Lock() + buffer := gfile.GetBinContents(path) + if len(buffer) > 0 { + os.Truncate(path, 0) + } + commLocker.UnLock() + if len(buffer) > 0 { + for _, v := range bufferToMsgs(buffer) { + commQueue.PushBack(v) + } + } +} + // 获取其他进程传递到当前进程的消息包,阻塞执行 func Receive() *Msg { if v := commQueue.PopFront(); v != nil { diff --git a/geg/other/test.go b/geg/other/test.go index 9a6703bc3..af3b1e9cb 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,62 +2,14 @@ package main import ( "fmt" - "net/http" - "github.com/tabalt/gracehttp" - "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/os/gfile" + "gitee.com/johng/gf/g/os/gtime" ) -func test() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "hello world") - }) - s := gracehttp.NewServer(":8888", nil, gracehttp.DEFAULT_READ_TIMEOUT, gracehttp.DEFAULT_WRITE_TIMEOUT) - err := s.ListenAndServe() - if err != nil { - fmt.Println(err) - } -} - -// 常见的二进制数据校验方式,生成校验结果 -func checksum(buffer []byte) uint32 { - var checksum uint32 - for _, b := range buffer { - checksum += uint32(b) - } - return checksum -} - -// 数据解包,防止黏包 -func bufferToMsgs(buffer []byte) []*gproc.Msg { - s := 0 - msgs := make([]*gproc.Msg, 0) - for s < len(buffer) { - fmt.Println(s) - length := gbinary.DecodeToInt(buffer[s : s + 4]) - if length < 0 || length > len(buffer) { - s++ - continue - } - checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) - checksum2 := checksum(buffer[s + 12 : s + length]) - if checksum1 != checksum2 { - s++ - continue - } - msgs = append(msgs, &gproc.Msg { - Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), - Data : buffer[s + 12 : s + length], - }) - s += length - } - return msgs -} - - -func main() { - b := []byte{26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33, 26, 0, 0, 0, 60, 109, 0, 0, 84, 5, 0, 0, 104, 101, 108, 108, 111, 32, 112, 114, 111, 99, 101, 115, 115, 33} - m := bufferToMsgs(b) - fmt.Println(m) -} +func main(){ + t1 := gfile.MTime("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") + t2 := gtime.Second() + fmt.Println(t1) + fmt.Println(t2) +} \ No newline at end of file From d2491241a2b368dbf220fa458d13069217624306 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 11 May 2018 12:57:05 +0800 Subject: [PATCH 11/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=BC=80=E5=8F=91=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO | 3 +- g/net/ghttp/ghttp_server.go | 40 +++++++---------- g/net/ghttp/ghttp_server_comm.go | 73 ++++++++++++++++++++++++-------- g/os/gfsnotify/gfsnotify.go | 7 --- g/os/gproc/gproc_manager.go | 21 +++++++-- g/os/gproc/gproc_proccess.go | 16 ++++--- geg/net/ghttp/hot_restart.go | 23 ++++++++++ geg/os/gproc/gproc2.go | 2 +- geg/other/test.go | 3 ++ 9 files changed, 125 insertions(+), 63 deletions(-) create mode 100644 geg/net/ghttp/hot_restart.go diff --git a/TODO b/TODO index d25b15f25..7b1f2297b 100644 --- a/TODO +++ b/TODO @@ -8,7 +8,8 @@ ON THE WAY: 7. 增加热编译工具,提高开发环境的开发/测试效率(媲美PHP开发效率); 8. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射; 9. orm增加更多数据库支持; - +10. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现; +11. 当二进制参数为nil时,gjson.LoadContent并将gjson.Json对象ToMap时会报错; diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 72e742b34..ee17765b1 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -8,20 +8,20 @@ package ghttp import ( "os" + "net" "sync" "errors" "strings" "reflect" "net/http" "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gcache" "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" - "net" - "gitee.com/johng/gf/g/os/gproc" - "gitee.com/johng/gf/g/encoding/gjson" ) const ( @@ -115,6 +115,7 @@ var doneChan = make(chan struct{}, 100000) // Web Server进程初始化 func init() { go func() { + // 等待run消息(Run方法调用) <- runChan // 主进程只负责创建子进程 if !gproc.IsChild() { @@ -122,6 +123,7 @@ func init() { } // 开启进程消息监听处理 handleProcessMsg() + // 服务执行完成,需要退出 doneChan <- struct{}{} }() } @@ -222,14 +224,16 @@ func (s *Server) Run() error { // 开启异步关闭队列处理循环 s.startCloseQueueLoop() + // 阻塞等待服务执行完成 <- doneChan + + glog.Printfln("web server pid %d exit successfully", gproc.Pid()) return nil } // 开启底层Web Server执行 func (s *Server) startServer(fdMap listenerFdMap) { // 开始执行底层Web Server创建,端口监听 - var wg sync.WaitGroup var server *gracefulServer if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { // HTTPS @@ -250,10 +254,9 @@ func (s *Server) startServer(fdMap listenerFdMap) { } for _, v := range array { - wg.Add(1) go func(item string) { if isFd { - tArray := strings.Split(item, ":") + tArray := strings.Split(item, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { server = s.newGracefulServer(item) @@ -265,7 +268,6 @@ func (s *Server) startServer(fdMap listenerFdMap) { if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { glog.Error(err) } - wg.Done() } }(v) } @@ -283,10 +285,9 @@ func (s *Server) startServer(fdMap listenerFdMap) { array = strings.Split(s.config.Addr, ",") } for _, v := range array { - wg.Add(1) go func(item string) { if isFd { - tArray := strings.Split(item, ":") + tArray := strings.Split(item, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { server = s.newGracefulServer(item) @@ -298,37 +299,28 @@ func (s *Server) startServer(fdMap listenerFdMap) { if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { glog.Error(err) } - wg.Done() } }(v) } s.status = 1 - - // 阻塞执行,直到所有Web Server退出 - wg.Wait() } // 重启Web Server func (s *Server) Restart() { // 如果是主进程,那么向所有子进程发送重启信号 if !gproc.IsChild() { - + procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) return } - //if pid, err := s.forkChildProcess(); err != nil { - // glog.Errorf("server restart failed: %v, continue serving\n", err) - //} else { - // glog.Printf("server restart successfully, new pid: %d\n", pid) - // s.Shutdown() - //} + sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) } // 关闭Web Server func (s *Server) Shutdown() { // 如果是主进程,那么向所有子进程发送关闭信号 if !gproc.IsChild() { - + procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) return } for _, v := range s.servers { @@ -336,17 +328,15 @@ func (s *Server) Shutdown() { } } - - // 获取当前监听的文件描述符信息,构造成map返回 func (s *Server) getListenerFdMap() map[string]string { - m := map[string]string{ + m := map[string]string { "http" : "", "https" : "", } for _, v := range s.servers { if f, e := v.listener.(*net.TCPListener).File(); e == nil { - str := v.addr + ":" + gconv.String(f.Fd()) + "," + str := v.addr + "#" + gconv.String(f.Fd()) + "," if v.isHttps { m["https"] += str } else { diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 7dca87c9c..975d8abda 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -8,41 +8,75 @@ package ghttp import ( + "os" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/encoding/gbinary" + "strings" + "gitee.com/johng/gf/g/util/gconv" "fmt" "gitee.com/johng/gf/g/encoding/gjson" - "os" ) const ( - gMSG_START = iota - gMSG_RESTART - gMSG_SHUTDOWN + gMSG_START = 10 + gMSG_RESTART = 20 + gMSG_CORESTART = 30 + gMSG_SHUTDOWN = 40 + gMSG_NEW_FORK = 50 ) - // 处理进程间消息 // 数据格式: 操作(8bit) | 参数(变长) func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { - fmt.Println(msg) - act := gbinary.DecodeToInt(msg.Data[0 : 1]) + fmt.Println(gproc.Pid(), gproc.IsChild(), msg) + act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] if gproc.IsChild() { + // 子进程 switch act { // 开启所有Web Server(根据消息启动) case gMSG_START: - sfm := bufferToServerFdMap(data) - for k, v := range sfm { - GetServer(k).startServer(v) + if len(data) > 0 { + sfm := bufferToServerFdMap(data) + for k, v := range sfm { + GetServer(k).startServer(v) + } + } else { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Server).startServer(nil) + } + }) } // 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 case gMSG_RESTART: - b, _ := gjson.Encode(getServerFdMap()) - sendProcessMsg(gproc.Ppid(), gMSG_RESTART, b) + // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 + sfm := getServerFdMap() + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + for name, m := range sfm { + for fdk, fdv := range m { + if len(fdv) > 0 { + s := "" + for _, item := range strings.Split(fdv, ",") { + array := strings.Split(item, "#") + fd := uintptr(gconv.Uint(array[1])) + s += fmt.Sprintf("%s#%d", array[0], len(p.GetAttr().Files)) + p.GetAttr().Files = append(p.GetAttr().Files, fd) + } + sfm[name][fdk] = strings.TrimRight(s, ",") + } + } + } + p.SetPpid(gproc.Ppid()) + p.Run() + fmt.Println(procManager) + b, _ := gjson.Encode(sfm) + sendProcessMsg(p.Pid(), gMSG_START, b) + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gconv.Bytes(p.Pid())) // 友好关闭服务链接并退出 case gMSG_SHUTDOWN: @@ -55,6 +89,7 @@ func handleProcessMsg() { } } else { + // 父进程 switch act { // 开启服务 case gMSG_START: @@ -64,12 +99,14 @@ func handleProcessMsg() { // 重启服务 case gMSG_RESTART: - // 创建新的服务进程,使用文件描述来监听同样的端口 - p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - p.Run() - sendProcessMsg(p.Pid(), gMSG_START, data) + // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 + procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) + + // 协调重启服务 + case gMSG_NEW_FORK: + //sendProcessMsg(p.Pid(), gMSG_START, data) // 关闭旧的服务进程 - sendProcessMsg(msg.Pid, gMSG_SHUTDOWN, nil) + //sendProcessMsg(msg.Pid, gMSG_SHUTDOWN, nil) // 关闭服务 case gMSG_SHUTDOWN: @@ -88,6 +125,6 @@ func sendProcessMsg(pid int, act int, data []byte) { // 生成一条满足Web Server进程通信协议的消息 func formatMsgBuffer(act int, data []byte) []byte { - return append(gbinary.EncodeInt8(int8(act)), data...) + return append(gbinary.EncodeUint8(uint8(act)), data...) } diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index c032f5e2c..cbed56312 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -13,9 +13,7 @@ import ( "gitee.com/johng/gf/g/os/glog" "github.com/fsnotify/fsnotify" "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/os/gcache" "gitee.com/johng/gf/g/os/grpool" - "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/glist" "gitee.com/johng/gf/g/container/gqueue" @@ -25,7 +23,6 @@ import ( type Watcher struct { watcher *fsnotify.Watcher // 底层fsnotify对象 events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 - eventCache *gcache.Cache // 用于进行事件过滤,当同一监听文件在10ms内出现相同事件,则过滤 closeChan chan struct{} // 关闭事件 callbacks *gmap.StringInterfaceMap // 监听的回调函数 } @@ -72,7 +69,6 @@ func New() (*Watcher, error) { w := &Watcher { watcher : watch, events : gqueue.New(), - eventCache : gcache.New(), closeChan : make(chan struct{}, 1), callbacks : gmap.NewStringInterfaceMap(), } @@ -132,9 +128,6 @@ func (w *Watcher) startWatchLoop() { // 监听事件 case ev := <- w.watcher.Events: - if !w.eventCache.Lock(ev.Name + ":" + gconv.String(ev.Op), 10) { - continue - } w.events.PushBack(&Event{ Path : ev.Name, Op : Op(ev.Op), diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index 336c2a931..dc2bb65c7 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -11,6 +11,7 @@ import ( "os" "gitee.com/johng/gf/g/container/gmap" "fmt" + "syscall" ) // 进程管理器 @@ -35,10 +36,9 @@ func NewProcess(path string, args []string, environment []string) *Process { p := &Process { path : path, args : make([]string, 0), - ppid : os.Getppid(), - attr : &os.ProcAttr { - Env : env, - Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, + ppid : os.Getpid(), + attr : &syscall.ProcAttr { + Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, }, } p.args = append(p.args, args[0]) @@ -64,6 +64,19 @@ func (m *Manager) GetProcess(pid int) *Process { return nil } +// 添加一个已存在进程到进程管理器中 +func (m *Manager) AddProcess(pid int) { + if process, err := os.FindProcess(pid); err == nil { + p := m.NewProcess("", nil, nil) + p.process = process + } +} + +// 移除进程管理器中的指定进程 +func (m *Manager) RemoveProcess(pid int) { + m.processes.Remove(pid) +} + // 获取所有的进程对象,构成列表返回 func (m *Manager) Processes() []*Process { processes := make([]*Process, 0) diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index 0420d1a17..b52459c55 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -10,6 +10,7 @@ import ( "os" "errors" "fmt" + "syscall" ) // 子进程 @@ -17,7 +18,8 @@ type Process struct { pm *Manager // 所属进程管理器 path string // 可执行文件绝对路径 args []string // 执行参数 - attr *os.ProcAttr // 进程属性 + //attr *os.ProcAttr // 进程属性 + attr *syscall.ProcAttr // 进程属性 ppid int // 自定义关联的父进程ID process *os.Process // 底层进程对象 } @@ -28,12 +30,12 @@ func (p *Process) Run() (int, error) { return p.Pid(), nil } p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) - if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { - p.process = process + if pid, err := syscall.ForkExec(p.path, p.args, p.attr); err == nil { + p.process, _ = os.FindProcess(pid) if p.pm != nil { - p.pm.processes.Set(process.Pid, p) + p.pm.processes.Set(pid, p) } - return process.Pid, nil + return pid, nil } else { return 0, err } @@ -68,11 +70,11 @@ func (p *Process) AddEnv(env []string) { } } -func (p *Process) SetAttr(attr *os.ProcAttr) { +func (p *Process) SetAttr(attr *syscall.ProcAttr) { p.attr = attr } -func (p *Process) GetAttr() *os.ProcAttr { +func (p *Process) GetAttr() *syscall.ProcAttr { return p.attr } diff --git a/geg/net/ghttp/hot_restart.go b/geg/net/ghttp/hot_restart.go new file mode 100644 index 000000000..4869e139c --- /dev/null +++ b/geg/net/ghttp/hot_restart.go @@ -0,0 +1,23 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("hello") + }) + s.BindHandler("/restart", func(r *ghttp.Request){ + r.Response.Writeln("restart server in 2 seconds") + gtime.SetTimeout(2*time.Second, func() { + r.Server.Restart() + }) + }) + s.SetPort(8199) + s.Run() +} \ No newline at end of file diff --git a/geg/os/gproc/gproc2.go b/geg/os/gproc/gproc2.go index a0a3f0b56..e92aed49c 100644 --- a/geg/os/gproc/gproc2.go +++ b/geg/os/gproc/gproc2.go @@ -6,6 +6,6 @@ import ( ) func main () { - err := gproc.Send(29260, "hello process!") + err := gproc.Send(26248, []byte{40}) fmt.Println(err) } diff --git a/geg/other/test.go b/geg/other/test.go index af3b1e9cb..1c91035f0 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -4,10 +4,13 @@ import ( "fmt" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/encoding/gbinary" ) func main(){ + fmt.Println(uint8(int(300))) + return t1 := gfile.MTime("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") t2 := gtime.Second() fmt.Println(t1) From dabc7891134db729c5f557fd93ea533b6132f6e0 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 11 May 2018 18:34:17 +0800 Subject: [PATCH 12/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E7=89=B9=E6=80=A7=E5=88=9D=E6=AD=A5=E5=BC=80?= =?UTF-8?q?=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 22 ++----- g/net/ghttp/ghttp_server_comm.go | 91 ++++++++++++++++++++++------ g/net/ghttp/ghttp_server_graceful.go | 15 ++++- g/os/gproc/gproc_comm.go | 2 +- g/os/gproc/gproc_manager.go | 30 ++++++--- g/os/gproc/gproc_proccess.go | 45 +++++++++++--- geg/net/ghttp/hot_restart.go | 12 ++-- 7 files changed, 155 insertions(+), 62 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index ee17765b1..d9daca7db 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -125,6 +125,10 @@ func init() { handleProcessMsg() // 服务执行完成,需要退出 doneChan <- struct{}{} + + if !gproc.IsChild() { + glog.Printfln("all web server shutdown smoothly") + } }() } @@ -226,8 +230,6 @@ func (s *Server) Run() error { // 阻塞等待服务执行完成 <- doneChan - - glog.Printfln("web server pid %d exit successfully", gproc.Pid()) return nil } @@ -262,7 +264,6 @@ func (s *Server) startServer(fdMap listenerFdMap) { server = s.newGracefulServer(item) } s.servers = append(s.servers, server) - glog.Printfln("https server started listening on %s", server.addr) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { @@ -293,7 +294,6 @@ func (s *Server) startServer(fdMap listenerFdMap) { server = s.newGracefulServer(item) } s.servers = append(s.servers, server) - glog.Printfln("http server started listening on %s", server.addr) if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { @@ -308,24 +308,12 @@ func (s *Server) startServer(fdMap listenerFdMap) { // 重启Web Server func (s *Server) Restart() { - // 如果是主进程,那么向所有子进程发送重启信号 - if !gproc.IsChild() { - procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) - return - } sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) } // 关闭Web Server func (s *Server) Shutdown() { - // 如果是主进程,那么向所有子进程发送关闭信号 - if !gproc.IsChild() { - procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) - return - } - for _, v := range s.servers { - v.shutdown() - } + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) } // 获取当前监听的文件描述符信息,构造成map返回 diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 975d8abda..bc0e4ebda 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -9,28 +9,34 @@ package ghttp import ( "os" - "gitee.com/johng/gf/g/os/gproc" - "gitee.com/johng/gf/g/encoding/gbinary" - "strings" - "gitee.com/johng/gf/g/util/gconv" "fmt" + "strings" + "syscall" + "os/signal" + "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/encoding/gbinary" ) const ( - gMSG_START = 10 - gMSG_RESTART = 20 - gMSG_CORESTART = 30 - gMSG_SHUTDOWN = 40 - gMSG_NEW_FORK = 50 + gMSG_START = 10 + gMSG_RESTART = 20 + gMSG_SHUTDOWN = 30 + gMSG_NEW_FORK = 40 + gMSG_REMOVE_PROC = 50 ) +// 进程信号量监听消息队列 +var procSignalChan = make(chan os.Signal) + // 处理进程间消息 // 数据格式: 操作(8bit) | 参数(变长) func handleProcessMsg() { + go handleProcessSignals() for { if msg := gproc.Receive(); msg != nil { - fmt.Println(gproc.Pid(), gproc.IsChild(), msg) + //fmt.Println(gproc.Pid(), gproc.IsChild(), msg) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] if gproc.IsChild() { @@ -64,7 +70,7 @@ func handleProcessMsg() { array := strings.Split(item, "#") fd := uintptr(gconv.Uint(array[1])) s += fmt.Sprintf("%s#%d", array[0], len(p.GetAttr().Files)) - p.GetAttr().Files = append(p.GetAttr().Files, fd) + p.GetAttr().Files = append(p.GetAttr().Files, os.NewFile(fd, "")) } sfm[name][fdk] = strings.TrimRight(s, ",") } @@ -72,19 +78,21 @@ func handleProcessMsg() { } p.SetPpid(gproc.Ppid()) p.Run() - fmt.Println(procManager) b, _ := gjson.Encode(sfm) sendProcessMsg(p.Pid(), gMSG_START, b) + sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gbinary.EncodeInt(p.Pid())) sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gconv.Bytes(p.Pid())) // 友好关闭服务链接并退出 case gMSG_SHUTDOWN: serverMapping.RLockFunc(func(m map[string]interface{}) { for _, v := range m { - v.(*Server).Shutdown() + for _, s := range v.(*Server).servers { + s.shutdown() + } } }) + sendProcessMsg(gproc.Ppid(), gMSG_REMOVE_PROC, gbinary.EncodeInt(gproc.Pid())) return } @@ -102,22 +110,67 @@ func handleProcessMsg() { // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) - // 协调重启服务 + // 新建子进程通知 case gMSG_NEW_FORK: - //sendProcessMsg(p.Pid(), gMSG_START, data) - // 关闭旧的服务进程 - //sendProcessMsg(msg.Pid, gMSG_SHUTDOWN, nil) + pid := gbinary.DecodeToInt(data) + procManager.AddProcess(pid) + + // 销毁子进程通知 + case gMSG_REMOVE_PROC: + pid := gbinary.DecodeToInt(data) + procManager.RemoveProcess(pid) + // 如果所有子进程都退出,那么主进程也主动退出 + if procManager.Size() == 0 { + return + } // 关闭服务 case gMSG_SHUTDOWN: procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) - + return } } } } } +// 信号量处理 +func handleProcessSignals() { + var sig os.Signal + signal.Notify( + procSignalChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGHUP, + syscall.SIGTERM, + syscall.SIGUSR1, + ) + for { + sig = <- procSignalChan + switch sig { + // 进程终止,停止所有子进程运行 + case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + return + + // 用户信号,重启服务 + case syscall.SIGUSR1: + sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) + + default: + } + } +} + +func onMainShutDown() { + procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) +} + +func onMainRemoveProc() { + procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) +} + // 向进程发送操作消息 func sendProcessMsg(pid int, act int, data []byte) { gproc.Send(pid, formatMsgBuffer(act, data)) diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 29688a5cc..b4d543ae3 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -14,6 +14,7 @@ import ( "net/http" "crypto/tls" "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/gproc" ) // 优雅的Web Server对象封装 @@ -92,8 +93,18 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { return s.doServe() } +// 获取服务协议字符串 +func (s *gracefulServer) getProto() string { + proto := "http" + if s.isHttps { + proto = "https" + } + return proto +} + // 开始执行Web Server服务处理 func (s *gracefulServer) doServe() error { + glog.Printfln("%d: %s server started listening on [%s]", gproc.Pid(), s.getProto(), s.addr) err := s.httpServer.Serve(s.listener) <-s.shutdownChan return err @@ -123,9 +134,9 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { // 执行请求优雅关闭 func (s *gracefulServer) shutdown() { if err := s.httpServer.Shutdown(context.Background()); err != nil { - glog.Errorf("server %s shutdown error: %v\n", s.addr, err) + glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - glog.Printf("server %s shutdown successfully\n", s.addr) + glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index de000c747..6f1632851 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -20,7 +20,7 @@ import ( ) const ( - // 由于子进程的temp dir有可能会和父进程不一致,影响进程间通信,这里统一使用环境变量设置 + // 由于子进程的temp dir有可能会和父进程不一致(特别是windows下),影响进程间通信,这里统一使用环境变量设置 gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" ) diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index dc2bb65c7..de6ad1751 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -9,9 +9,13 @@ package gproc import ( "os" - "gitee.com/johng/gf/g/container/gmap" "fmt" - "syscall" + "gitee.com/johng/gf/g/container/gmap" + "strings" +) + +const ( + gCHILD_ARGS_MARK_NAME = "--gproc-child" ) // 进程管理器 @@ -37,14 +41,20 @@ func NewProcess(path string, args []string, environment []string) *Process { path : path, args : make([]string, 0), ppid : os.Getpid(), - attr : &syscall.ProcAttr { - Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, + attr : &os.ProcAttr { + Env : env, + Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, }, } - p.args = append(p.args, args[0]) - p.args = append(p.args, "--gproc-child") + if len(args) > 0 { + p.args = append(p.args, args[0]) + } + // 判断是否加上子进程标识 + if len(args) == 1 || (len(args) > 1 && !strings.EqualFold(args[1], gCHILD_ARGS_MARK_NAME)) { + p.args = append(p.args, gCHILD_ARGS_MARK_NAME) + } if len(args) > 1 { - p.args = append(p.args, args[1:]...) + p.args = append(p.args, args[1 : ]...) } return p } @@ -69,6 +79,7 @@ func (m *Manager) AddProcess(pid int) { if process, err := os.FindProcess(pid); err == nil { p := m.NewProcess("", nil, nil) p.process = process + m.processes.Set(pid, p) } } @@ -133,6 +144,11 @@ func (m *Manager) SendTo(pid int, data interface{}) error { return Send(pid, data) } +// 清空管理器 +func (m *Manager) Clear() { + m.processes.Clear() +} + // 当前进程总数 func (m *Manager) Size() int { return m.processes.Size() diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index b52459c55..1fa3e6de4 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -8,9 +8,8 @@ package gproc import ( "os" - "errors" "fmt" - "syscall" + "errors" ) // 子进程 @@ -18,8 +17,7 @@ type Process struct { pm *Manager // 所属进程管理器 path string // 可执行文件绝对路径 args []string // 执行参数 - //attr *os.ProcAttr // 进程属性 - attr *syscall.ProcAttr // 进程属性 + attr *os.ProcAttr // 进程属性 ppid int // 自定义关联的父进程ID process *os.Process // 底层进程对象 } @@ -30,17 +28,44 @@ func (p *Process) Run() (int, error) { return p.Pid(), nil } p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) - if pid, err := syscall.ForkExec(p.path, p.args, p.attr); err == nil { - p.process, _ = os.FindProcess(pid) + if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { + p.process = process if p.pm != nil { - p.pm.processes.Set(pid, p) + p.pm.processes.Set(process.Pid, p) } - return pid, nil + return process.Pid, nil } else { return 0, err } } +// 运行进程,守护进程,主进程退出后不退出 +//func (p *Process) RunDaemon() (int, error) { +// if p.process != nil { +// return p.Pid(), nil +// } +// p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) +// procAttr := &syscall.ProcAttr { +// Dir : p.attr.Dir, +// Env : p.attr.Env, +// Sys : p.attr.Sys, +// Files : make([]uintptr, 0), +// } +// // 将os.ProcAttr.Files转换为底层的syscall.ProcAttr.Files +// for _, f := range p.attr.Files { +// procAttr.Files = append(procAttr.Files, f.Fd()) +// } +// if pid, err := syscall.ForkExec(p.path, p.args, procAttr); err == nil { +// p.process, _ = os.FindProcess(pid) +// if p.pm != nil { +// p.pm.processes.Set(pid, p) +// } +// return pid, nil +// } else { +// return 0, err +// } +//} + func (p *Process) SetManager(m *Manager) { p.pm = m } @@ -70,11 +95,11 @@ func (p *Process) AddEnv(env []string) { } } -func (p *Process) SetAttr(attr *syscall.ProcAttr) { +func (p *Process) SetAttr(attr *os.ProcAttr) { p.attr = attr } -func (p *Process) GetAttr() *syscall.ProcAttr { +func (p *Process) GetAttr() *os.ProcAttr { return p.attr } diff --git a/geg/net/ghttp/hot_restart.go b/geg/net/ghttp/hot_restart.go index 4869e139c..c227c05ec 100644 --- a/geg/net/ghttp/hot_restart.go +++ b/geg/net/ghttp/hot_restart.go @@ -3,8 +3,6 @@ package main import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/net/ghttp" - "gitee.com/johng/gf/g/os/gtime" - "time" ) func main() { @@ -13,10 +11,12 @@ func main() { r.Response.Writeln("hello") }) s.BindHandler("/restart", func(r *ghttp.Request){ - r.Response.Writeln("restart server in 2 seconds") - gtime.SetTimeout(2*time.Second, func() { - r.Server.Restart() - }) + r.Response.Writeln("restart server") + r.Server.Restart() + }) + s.BindHandler("/shutdown", func(r *ghttp.Request){ + r.Response.Writeln("shutdown server") + r.Server.Shutdown() }) s.SetPort(8199) s.Run() From 3c5ed69f23068ab35885625f38a493257430696c Mon Sep 17 00:00:00 2001 From: John Date: Fri, 11 May 2018 23:17:25 +0800 Subject: [PATCH 13/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E6=B5=8B=E8=AF=95=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 40 ++--- g/net/ghttp/ghttp_server_comm.go | 148 ++++++++---------- g/net/ghttp/ghttp_server_comm_child.go | 103 ++++++++++++ g/net/ghttp/ghttp_server_comm_main.go | 73 +++++++++ g/net/ghttp/ghttp_server_graceful.go | 4 +- g/os/gproc/gproc_manager.go | 5 + geg/net/ghttp/hot_restart/multi_port.go | 23 +++ .../hot_restart/multi_port_and_server.go | 32 ++++ .../{hot_restart.go => hot_restart/simple.go} | 0 geg/os/gproc/gproc2.go | 2 +- geg/other/test.go | 41 ++++- 11 files changed, 352 insertions(+), 119 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_comm_child.go create mode 100644 g/net/ghttp/ghttp_server_comm_main.go create mode 100644 geg/net/ghttp/hot_restart/multi_port.go create mode 100644 geg/net/ghttp/hot_restart/multi_port_and_server.go rename geg/net/ghttp/{hot_restart.go => hot_restart/simple.go} (100%) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index d9daca7db..71108b429 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -18,7 +18,6 @@ import ( "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gcache" "gitee.com/johng/gf/g/util/gconv" - "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" @@ -111,6 +110,8 @@ var procManager = gproc.NewManager() var runChan = make(chan struct{}, 100000) // 已完成的run消息队列 var doneChan = make(chan struct{}, 100000) +// 阻塞消息队列,用于ghttp.Wait +var waitChan = make(chan struct{}, 0) // Web Server进程初始化 func init() { @@ -122,41 +123,22 @@ func init() { sendProcessMsg(os.Getpid(), gMSG_START, nil) } // 开启进程消息监听处理 - handleProcessMsg() + handleProcessMsgAndSignal() // 服务执行完成,需要退出 doneChan <- struct{}{} if !gproc.IsChild() { - glog.Printfln("all web server shutdown smoothly") + glog.Printfln("%d: all web server shutdown smoothly", gproc.Pid()) } + + // 停止进程等待 + close(waitChan) }() } -// 获取所有Web Server的文件描述符map -func getServerFdMap() map[string]listenerFdMap { - sfm := make(map[string]listenerFdMap) - serverMapping.RLockFunc(func(m map[string]interface{}) { - for k, v := range m { - sfm[k] = v.(*Server).getListenerFdMap() - } - }) - return sfm -} - -// 二进制转换为FdMap -func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { - sfm := make(map[string]listenerFdMap) - if len(buffer) > 0 { - j, _ := gjson.LoadContent(buffer, "json") - for k, _ := range j.ToMap() { - m := make(map[string]string) - for k, v := range j.GetMap(k) { - m[k] = gconv.String(v) - } - sfm[k] = m - } - } - return sfm +// 阻塞等待所有Web Server停止,常用于多Web Server场景,以及需要将Web Server异步运行的场景 +func Wait() { + <- waitChan } // 获取/创建一个默认配置的HTTP Server(默认监听端口是80) @@ -267,6 +249,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + s.servers = s.servers[0 : len(s.servers) - 1] glog.Error(err) } } @@ -297,6 +280,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + s.servers = s.servers[0 : len(s.servers) - 1] glog.Error(err) } } diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index bc0e4ebda..c4240296b 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -9,13 +9,12 @@ package ghttp import ( "os" - "fmt" - "strings" "syscall" "os/signal" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" ) @@ -25,108 +24,65 @@ const ( gMSG_SHUTDOWN = 30 gMSG_NEW_FORK = 40 gMSG_REMOVE_PROC = 50 + gMSG_HEARTBEAT = 60 + + gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 + gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 ) // 进程信号量监听消息队列 -var procSignalChan = make(chan os.Signal) +var procSignalChan = make(chan os.Signal) + +// (主子进程)在第一次创建子进程成功之后才会开始心跳检测,同理对应超时时间才会生效 +var heartbeatStarted = gtype.NewBool() + +// 处理进程信号量监控以及进程间消息通信 +func handleProcessMsgAndSignal() { + go handleProcessSignal() + if gproc.IsChild() { + go handleChildProcessHeartbeat() + } else { + go handleMainProcessHeartbeat() + } + handleProcessMsg() +} // 处理进程间消息 // 数据格式: 操作(8bit) | 参数(变长) func handleProcessMsg() { - go handleProcessSignals() for { if msg := gproc.Receive(); msg != nil { - //fmt.Println(gproc.Pid(), gproc.IsChild(), msg) + // 记录消息日志,用于调试 + //gfile.PutContentsAppend("/tmp/gproc-log", + // gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data), + //) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] if gproc.IsChild() { // 子进程 switch act { - // 开启所有Web Server(根据消息启动) - case gMSG_START: - if len(data) > 0 { - sfm := bufferToServerFdMap(data) - for k, v := range sfm { - GetServer(k).startServer(v) - } - } else { - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - v.(*Server).startServer(nil) - } - }) - } - - // 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 - case gMSG_RESTART: - // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 - sfm := getServerFdMap() - p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - for name, m := range sfm { - for fdk, fdv := range m { - if len(fdv) > 0 { - s := "" - for _, item := range strings.Split(fdv, ",") { - array := strings.Split(item, "#") - fd := uintptr(gconv.Uint(array[1])) - s += fmt.Sprintf("%s#%d", array[0], len(p.GetAttr().Files)) - p.GetAttr().Files = append(p.GetAttr().Files, os.NewFile(fd, "")) - } - sfm[name][fdk] = strings.TrimRight(s, ",") - } - } - } - p.SetPpid(gproc.Ppid()) - p.Run() - b, _ := gjson.Encode(sfm) - sendProcessMsg(p.Pid(), gMSG_START, b) - sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gbinary.EncodeInt(p.Pid())) - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - - // 友好关闭服务链接并退出 + case gMSG_START: onCommChildStart(msg.Pid, data) + case gMSG_RESTART: onCommChildRestart(msg.Pid, data) + case gMSG_HEARTBEAT: onCommChildHeartbeat(msg.Pid, data) case gMSG_SHUTDOWN: - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - for _, s := range v.(*Server).servers { - s.shutdown() - } - } - }) - sendProcessMsg(gproc.Ppid(), gMSG_REMOVE_PROC, gbinary.EncodeInt(gproc.Pid())) + onCommChildShutdown(msg.Pid, data) return - } } else { // 父进程 switch act { - // 开启服务 - case gMSG_START: - p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - p.Run() - sendProcessMsg(p.Pid(), gMSG_START, nil) - - // 重启服务 - case gMSG_RESTART: - // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 - procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) - - // 新建子进程通知 - case gMSG_NEW_FORK: - pid := gbinary.DecodeToInt(data) - procManager.AddProcess(pid) - - // 销毁子进程通知 + case gMSG_START: onCommMainStart(msg.Pid, data) + case gMSG_RESTART: onCommMainRestart(msg.Pid, data) + case gMSG_NEW_FORK: onCommMainNewFork(msg.Pid, data) + case gMSG_HEARTBEAT: onCommMainHeartbeat(msg.Pid, data) case gMSG_REMOVE_PROC: - pid := gbinary.DecodeToInt(data) - procManager.RemoveProcess(pid) + onCommMainRemoveProc(msg.Pid, data) // 如果所有子进程都退出,那么主进程也主动退出 if procManager.Size() == 0 { return } - - // 关闭服务 case gMSG_SHUTDOWN: - procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) + onCommMainShutdown(msg.Pid, data) return } } @@ -135,7 +91,7 @@ func handleProcessMsg() { } // 信号量处理 -func handleProcessSignals() { +func handleProcessSignal() { var sig os.Signal signal.Notify( procSignalChan, @@ -163,14 +119,6 @@ func handleProcessSignals() { } } -func onMainShutDown() { - procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) -} - -func onMainRemoveProc() { - procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) -} - // 向进程发送操作消息 func sendProcessMsg(pid int, act int, data []byte) { gproc.Send(pid, formatMsgBuffer(act, data)) @@ -181,3 +129,29 @@ func formatMsgBuffer(act int, data []byte) []byte { return append(gbinary.EncodeUint8(uint8(act)), data...) } +// 获取所有Web Server的文件描述符map +func getServerFdMap() map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + serverMapping.RLockFunc(func(m map[string]interface{}) { + for k, v := range m { + sfm[k] = v.(*Server).getListenerFdMap() + } + }) + return sfm +} + +// 二进制转换为FdMap +func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { + sfm := make(map[string]listenerFdMap) + if len(buffer) > 0 { + j, _ := gjson.LoadContent(buffer, "json") + for k, _ := range j.ToMap() { + m := make(map[string]string) + for k, v := range j.GetMap(k) { + m[k] = gconv.String(v) + } + sfm[k] = m + } + } + return sfm +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go new file mode 100644 index 000000000..fe15f93ab --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -0,0 +1,103 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. +// Web Server进程间通信 - 子进程 + +package ghttp + +import ( + "os" + "fmt" + "time" + "strings" + "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/encoding/gjson" + "gitee.com/johng/gf/g/container/gtype" + "gitee.com/johng/gf/g/encoding/gbinary" +) + +// (子进程)上一次从主进程接收心跳的时间戳 +var lastHeartbeatTime = gtype.NewInt() + +// 开启所有Web Server(根据消息启动) +func onCommChildStart(pid int, data []byte) { + if len(data) > 0 { + sfm := bufferToServerFdMap(data) + for k, v := range sfm { + GetServer(k).startServer(v) + } + } else { + fmt.Println(serverMapping) + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Server).startServer(nil) + } + }) + } + heartbeatStarted.Set(true) +} + +// 心跳消息 +func onCommChildHeartbeat(pid int, data []byte) { + //glog.Printfln("%d: child heartbeat", gproc.Pid()) + lastHeartbeatTime.Set(int(gtime.Millisecond())) +} + +// 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 +func onCommChildRestart(pid int, data []byte) { + // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 + sfm := getServerFdMap() + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + // 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理,以便子进程获取到正确的fd + for name, m := range sfm { + for fdk, fdv := range m { + if len(fdv) > 0 { + s := "" + for _, item := range strings.Split(fdv, ",") { + array := strings.Split(item, "#") + fd := uintptr(gconv.Uint(array[1])) + s += fmt.Sprintf("%s#%d,", array[0], len(p.GetAttr().Files)) + p.GetAttr().Files = append(p.GetAttr().Files, os.NewFile(fd, "")) + } + sfm[name][fdk] = strings.TrimRight(s, ",") + } + } + } + p.SetPpid(gproc.Ppid()) + p.Run() + // 编码,通信 + b, _ := gjson.Encode(sfm) + sendProcessMsg(p.Pid(), gMSG_START, b) + sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gbinary.EncodeInt(p.Pid())) + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) +} + +// 友好关闭服务链接并退出 +func onCommChildShutdown(pid int, data []byte) { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + for _, s := range v.(*Server).servers { + s.shutdown() + } + } + }) + sendProcessMsg(gproc.Ppid(), gMSG_REMOVE_PROC, gbinary.EncodeInt(gproc.Pid())) +} + +// 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 +func handleChildProcessHeartbeat() { + for { + time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) + sendProcessMsg(gproc.Ppid(), gMSG_HEARTBEAT, nil) + // 超过时间没有接收到主进程心跳,自动关闭退出 + if heartbeatStarted.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + // 子进程有时会无法退出,这里直接使用exit,而不是return + os.Exit(0) + } + } +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go new file mode 100644 index 000000000..273b10f93 --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -0,0 +1,73 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. +// Web Server进程间通信 - 主进程 + +package ghttp + +import ( + "os" + "time" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/encoding/gbinary" +) + +// (主进程)主进程与子进程上一次活跃时间映射map +var procUpdateMap = gmap.NewIntIntMap() + +// 开启服务 +func onCommMainStart(pid int, data []byte) { + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Run() + sendProcessMsg(p.Pid(), gMSG_START, nil) +} + +// 心跳处理 +func onCommMainHeartbeat(pid int, data []byte) { + //glog.Printfln("%d: main heartbeat", gproc.Pid()) + procUpdateMap.Set(pid, int(gtime.Millisecond())) +} + +// 重启服务 +func onCommMainRestart(pid int, data []byte) { + // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 + procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) +} + +// 新建子进程通知 +func onCommMainNewFork(pid int, data []byte) { + procManager.AddProcess(gbinary.DecodeToInt(data)) + heartbeatStarted.Set(true) +} + +// 销毁子进程通知 +func onCommMainRemoveProc(pid int, data []byte) { + procManager.RemoveProcess(gbinary.DecodeToInt(data)) +} + +// 关闭服务,通知所有子进程退出 +func onCommMainShutdown(pid int, data []byte) { + procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) +} + +// 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 +func handleMainProcessHeartbeat() { + for { + time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) + procManager.Send(formatMsgBuffer(gMSG_HEARTBEAT, nil)) + // 清理过期进程 + if heartbeatStarted.Val() { + for _, pid := range procManager.Pids() { + if int(gtime.Millisecond()) - procUpdateMap.Get(pid) > gPROC_HEARTBEAT_TIMEOUT { + // 这里需要手动从进程管理器中去掉该进程 + procManager.RemoveProcess(pid) + sendProcessMsg(pid, gMSG_SHUTDOWN, nil) + return + } + } + } + } +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index b4d543ae3..5558c3fec 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -118,13 +118,13 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { f := os.NewFile(s.fd, "") ln, err = net.FileListener(f) if err != nil { - err = fmt.Errorf("net.FileListener error: %v", err) + err = fmt.Errorf("%d: net.FileListener error: %v", gproc.Pid(), err) return nil, err } } else { ln, err = net.Listen("tcp", addr) if err != nil { - err = fmt.Errorf("net.Listen error: %v", err) + err = fmt.Errorf("%d: net.Listen error: %v", gproc.Pid(), err) return nil, err } } diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index de6ad1751..1e3119682 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -99,6 +99,11 @@ func (m *Manager) Processes() []*Process { return processes } +// 获取所有的进程pid,构成列表返回 +func (m *Manager) Pids() []int { + return m.processes.Keys() +} + // 等待所有子进程结束 func (m *Manager) WaitAll() { processes := m.Processes() diff --git a/geg/net/ghttp/hot_restart/multi_port.go b/geg/net/ghttp/hot_restart/multi_port.go new file mode 100644 index 000000000..359520ab8 --- /dev/null +++ b/geg/net/ghttp/hot_restart/multi_port.go @@ -0,0 +1,23 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("hello") + }) + s.BindHandler("/restart", func(r *ghttp.Request){ + r.Response.Writeln("restart server") + r.Server.Restart() + }) + s.BindHandler("/shutdown", func(r *ghttp.Request){ + r.Response.Writeln("shutdown server") + r.Server.Shutdown() + }) + s.SetPort(8199, 8200) + s.Run() +} \ No newline at end of file diff --git a/geg/net/ghttp/hot_restart/multi_port_and_server.go b/geg/net/ghttp/hot_restart/multi_port_and_server.go new file mode 100644 index 000000000..f7dbfac35 --- /dev/null +++ b/geg/net/ghttp/hot_restart/multi_port_and_server.go @@ -0,0 +1,32 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/net/ghttp" +) + +func main() { + s1 := g.Server("s1") + s1.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("hello s1") + }) + s1.BindHandler("/restart", func(r *ghttp.Request){ + r.Response.Writeln("restart server") + r.Server.Restart() + }) + s1.BindHandler("/shutdown", func(r *ghttp.Request){ + r.Response.Writeln("shutdown server") + r.Server.Shutdown() + }) + s1.SetPort(8199, 8200) + go s1.Run() + + s2 := g.Server("s2") + s2.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("hello s2") + }) + s2.SetPort(8300, 8080) + go s2.Run() + + ghttp.Wait() +} \ No newline at end of file diff --git a/geg/net/ghttp/hot_restart.go b/geg/net/ghttp/hot_restart/simple.go similarity index 100% rename from geg/net/ghttp/hot_restart.go rename to geg/net/ghttp/hot_restart/simple.go diff --git a/geg/os/gproc/gproc2.go b/geg/os/gproc/gproc2.go index e92aed49c..c48ddd7ec 100644 --- a/geg/os/gproc/gproc2.go +++ b/geg/os/gproc/gproc2.go @@ -6,6 +6,6 @@ import ( ) func main () { - err := gproc.Send(26248, []byte{40}) + err := gproc.Send(23504, []byte{30}) fmt.Println(err) } diff --git a/geg/other/test.go b/geg/other/test.go index 1c91035f0..ce225f777 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -5,11 +5,50 @@ import ( "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/os/gproc" ) +// 数据解包,防止黏包 +func bufferToMsgs(buffer []byte) []*gproc.Msg { + s := 0 + msgs := make([]*gproc.Msg, 0) + for s < len(buffer) { + length := gbinary.DecodeToInt(buffer[s : s + 4]) + if length < 0 || length > len(buffer) { + s++ + continue + } + checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) + checksum2 := checksum(buffer[s + 12 : s + length]) + if checksum1 != checksum2 { + s++ + continue + } + msgs = append(msgs, &gproc.Msg { + Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), + Data : buffer[s + 12 : s + length], + }) + s += length + } + return msgs +} + +// 常见的二进制数据校验方式,生成校验结果 +func checksum(buffer []byte) uint32 { + var checksum uint32 + for _, b := range buffer { + checksum += uint32(b) + } + return checksum +} func main(){ - fmt.Println(uint8(int(300))) + b := gfile.GetBinContents("/tmp/gproc/27501") + for _, msg := range bufferToMsgs(b) { + fmt.Println(msg.Pid) + fmt.Println(msg.Data) + } + return t1 := gfile.MTime("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") t2 := gtime.Second() From 99403ffc4b889e7486a521b5a21c53d1a61593fc Mon Sep 17 00:00:00 2001 From: John Date: Fri, 11 May 2018 23:31:35 +0800 Subject: [PATCH 14/31] =?UTF-8?q?=E5=8E=BB=E6=8E=89Web=20Server=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E9=97=B4=E9=80=9A=E4=BF=A1=E8=B0=83=E8=AF=95=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_comm_child.go | 1 - 1 file changed, 1 deletion(-) diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index fe15f93ab..f771d689a 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -31,7 +31,6 @@ func onCommChildStart(pid int, data []byte) { GetServer(k).startServer(v) } } else { - fmt.Println(serverMapping) serverMapping.RLockFunc(func(m map[string]interface{}) { for _, v := range m { v.(*Server).startServer(nil) From 206d2008824632fdd724cf0e5ccdd6c570317547 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 12 May 2018 10:37:42 +0800 Subject: [PATCH 15/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E5=9C=A8Li?= =?UTF-8?q?nux=E4=B8=8B=E7=9A=84=E5=8A=9F=E8=83=BD=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=8C=E4=BB=8A=E5=A4=A9=E5=B8=A6=E8=80=81=E5=A9=86=E5=AD=A9?= =?UTF-8?q?=E5=AD=90=E5=8E=BB=E6=91=98=E8=93=9D=E8=8E=93=EF=BC=8C=E5=9B=9E?= =?UTF-8?q?=E6=9D=A5=E7=BB=A7=E7=BB=AD=E5=AE=8C=E6=88=90Windows=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_comm.go | 13 +++++++++++++ g/net/ghttp/ghttp_server_comm_child.go | 15 ++++++++++----- g/net/ghttp/ghttp_server_comm_main.go | 10 +++++++--- g/os/gproc/gproc.go | 14 ++++++++++++-- g/os/gproc/gproc_proccess.go | 4 +++- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index c4240296b..0ab92295d 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -12,6 +12,7 @@ import ( "syscall" "os/signal" "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" @@ -59,7 +60,13 @@ func handleProcessMsg() { act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] if gproc.IsChild() { + // =============== // 子进程 + // =============== + // 任何与父进程的通信都会更新最后通信时间 + if msg.Pid == gproc.Ppid() { + lastHeartbeatTime.Set(int(gtime.Millisecond())) + } switch act { case gMSG_START: onCommChildStart(msg.Pid, data) case gMSG_RESTART: onCommChildRestart(msg.Pid, data) @@ -69,7 +76,13 @@ func handleProcessMsg() { return } } else { + // =============== // 父进程 + // =============== + // 任何进程消息都会自动更新最后通信时间记录 + if msg.Pid != gproc.Pid() { + updateProcessCommTime(msg.Pid) + } switch act { case gMSG_START: onCommMainStart(msg.Pid, data) case gMSG_RESTART: onCommMainRestart(msg.Pid, data) diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index f771d689a..6a7bbdc7b 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -18,6 +18,7 @@ import ( "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/os/glog" ) // (子进程)上一次从主进程接收心跳的时间戳 @@ -37,12 +38,17 @@ func onCommChildStart(pid int, data []byte) { } }) } + // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 + sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, nil) + // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程销毁 + if os.Getppid() != gproc.Ppid() { + sendProcessMsg(os.Getppid(), gMSG_SHUTDOWN, nil) + } heartbeatStarted.Set(true) } // 心跳消息 func onCommChildHeartbeat(pid int, data []byte) { - //glog.Printfln("%d: child heartbeat", gproc.Pid()) lastHeartbeatTime.Set(int(gtime.Millisecond())) } @@ -70,9 +76,7 @@ func onCommChildRestart(pid int, data []byte) { p.Run() // 编码,通信 b, _ := gjson.Encode(sfm) - sendProcessMsg(p.Pid(), gMSG_START, b) - sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, gbinary.EncodeInt(p.Pid())) - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + sendProcessMsg(p.Pid(), gMSG_START, b) } // 友好关闭服务链接并退出 @@ -95,7 +99,8 @@ func handleChildProcessHeartbeat() { // 超过时间没有接收到主进程心跳,自动关闭退出 if heartbeatStarted.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - // 子进程有时会无法退出,这里直接使用exit,而不是return + // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return + glog.Errorfln("%d heartbeat timeout, exit", gproc.Pid()) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 273b10f93..4ff63b8cc 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -27,8 +27,7 @@ func onCommMainStart(pid int, data []byte) { // 心跳处理 func onCommMainHeartbeat(pid int, data []byte) { - //glog.Printfln("%d: main heartbeat", gproc.Pid()) - procUpdateMap.Set(pid, int(gtime.Millisecond())) + updateProcessCommTime(pid) } // 重启服务 @@ -39,7 +38,7 @@ func onCommMainRestart(pid int, data []byte) { // 新建子进程通知 func onCommMainNewFork(pid int, data []byte) { - procManager.AddProcess(gbinary.DecodeToInt(data)) + procManager.AddProcess(pid) heartbeatStarted.Set(true) } @@ -53,6 +52,11 @@ func onCommMainShutdown(pid int, data []byte) { procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) } +// 更新指定进程的通信时间记录 +func updateProcessCommTime(pid int) { + procUpdateMap.Set(pid, int(gtime.Millisecond())) +} + // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 func handleMainProcessHeartbeat() { for { diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go index 1131b6533..353be58fb 100644 --- a/g/os/gproc/gproc.go +++ b/g/os/gproc/gproc.go @@ -21,9 +21,19 @@ func Pid() int { return os.Getpid() } -// 获取父进程ID +// 获取父进程ID(gproc父进程,不存在时则使用系统父进程) func Ppid() int { - return gconv.Int(os.Getenv(gPROC_ENV_KEY_PPID_KEY)) + // gPROC_ENV_KEY_PPID_KEY为gproc包自定义的父进程 + ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) + if ppidValue != "" { + return gconv.Int(ppidValue) + } + return os.Getppid() +} + +// 获取父进程ID(系统父进程) +func PpidOfOs() int { + return os.Getppid() } // 判断当前进程是否为gproc创建的子进程 diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index 1fa3e6de4..c414335d0 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -27,7 +27,9 @@ func (p *Process) Run() (int, error) { if p.process != nil { return p.Pid(), nil } - p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) + if p.ppid > 0 { + p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) + } if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { p.process = process if p.pm != nil { From 71fb05ec44e4a99d0e5bb670b0919bf54f895694 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 May 2018 00:17:12 +0800 Subject: [PATCH 16/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=9C=A8windows=E4=B8=8B=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=AD=EF=BC=8C=E9=9C=80=E8=A6=81=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E4=B8=80=E4=BA=9B=E4=B8=8D=E5=90=8C=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 9 +- g/net/ghttp/ghttp_server_comm.go | 64 +++++---- g/net/ghttp/ghttp_server_comm_child.go | 72 +++++----- g/net/ghttp/ghttp_server_comm_main.go | 6 +- g/net/ghttp/ghttp_server_comm_signal_unix.go | 45 +++++++ .../ghttp/ghttp_server_comm_signal_windows.go | 12 ++ g/net/ghttp/ghttp_server_graceful.go | 10 ++ g/os/gproc/gproc.go | 6 +- g/os/gproc/gproc_comm.go | 16 ++- g/os/gproc/gproc_manager.go | 49 ++++--- g/os/gproc/gproc_proccess.go | 123 ++++-------------- geg/os/gproc/cmd.go | 15 +++ geg/os/gproc/gproc.go | 11 +- 13 files changed, 239 insertions(+), 199 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_comm_signal_unix.go create mode 100644 g/net/ghttp/ghttp_server_comm_signal_windows.go create mode 100644 geg/os/gproc/cmd.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 71108b429..791b14bf9 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -21,6 +21,7 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" + "runtime" ) const ( @@ -239,7 +240,8 @@ func (s *Server) startServer(fdMap listenerFdMap) { for _, v := range array { go func(item string) { - if isFd { + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if isFd && runtime.GOOS != "windows" { tArray := strings.Split(item, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { @@ -257,7 +259,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { } } // HTTP - if s.servedCount.Val() == 0 && len(s.config.Addr) == 0 { + if len(s.config.Addr) == 0 { s.config.Addr = gDEFAULT_HTTP_ADDR } var array []string @@ -270,7 +272,8 @@ func (s *Server) startServer(fdMap listenerFdMap) { } for _, v := range array { go func(item string) { - if isFd { + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if isFd && runtime.GOOS != "windows" { tArray := strings.Split(item, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 0ab92295d..7b410f968 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -9,14 +9,13 @@ package ghttp import ( "os" - "syscall" - "os/signal" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" + "fmt" ) const ( @@ -54,9 +53,9 @@ func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { // 记录消息日志,用于调试 - //gfile.PutContentsAppend("/tmp/gproc-log", - // gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data), - //) + content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) + fmt.Print(content) + //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] if gproc.IsChild() { @@ -64,7 +63,7 @@ func handleProcessMsg() { // 子进程 // =============== // 任何与父进程的通信都会更新最后通信时间 - if msg.Pid == gproc.Ppid() { + if msg.Pid == gproc.PPid() { lastHeartbeatTime.Set(int(gtime.Millisecond())) } switch act { @@ -103,35 +102,6 @@ func handleProcessMsg() { } } -// 信号量处理 -func handleProcessSignal() { - var sig os.Signal - signal.Notify( - procSignalChan, - syscall.SIGINT, - syscall.SIGQUIT, - syscall.SIGKILL, - syscall.SIGHUP, - syscall.SIGTERM, - syscall.SIGUSR1, - ) - for { - sig = <- procSignalChan - switch sig { - // 进程终止,停止所有子进程运行 - case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - return - - // 用户信号,重启服务 - case syscall.SIGUSR1: - sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) - - default: - } - } -} - // 向进程发送操作消息 func sendProcessMsg(pid int, act int, data []byte) { gproc.Send(pid, formatMsgBuffer(act, data)) @@ -167,4 +137,28 @@ func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap { } } return sfm +} + +// 关优雅闭进程所有端口的Web Server服务 +// 注意,只是关闭Web Server服务,并不是退出进程 +func shutdownWebServers() { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + for _, s := range v.(*Server).servers { + s.shutdown() + } + } + }) +} + +// 强制关闭进程所有端口的Web Server服务 +// 注意,只是关闭Web Server服务,并不是退出进程 +func closeWebServers() { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + for _, s := range v.(*Server).servers { + s.close() + } + } + }) } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index 6a7bbdc7b..cffa5dbf9 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -12,13 +12,13 @@ import ( "fmt" "time" "strings" + "runtime" + "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" - "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/os/glog" ) // (子进程)上一次从主进程接收心跳的时间戳 @@ -39,9 +39,10 @@ func onCommChildStart(pid int, data []byte) { }) } // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 - sendProcessMsg(gproc.Ppid(), gMSG_NEW_FORK, nil) + sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程销毁 - if os.Getppid() != gproc.Ppid() { + if os.Getppid() != gproc.PPid() { + //glog.Printfln("%d: ask os.ppid %d to exit, proc.ppid:%d", gproc.Pid(), os.Getppid(), gproc.PPid()) sendProcessMsg(os.Getppid(), gMSG_SHUTDOWN, nil) } heartbeatStarted.Set(true) @@ -49,58 +50,63 @@ func onCommChildStart(pid int, data []byte) { // 心跳消息 func onCommChildHeartbeat(pid int, data []byte) { + //glog.Printfln("%d: update heartbeat", gproc.Pid()) lastHeartbeatTime.Set(int(gtime.Millisecond())) } // 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 func onCommChildRestart(pid int, data []byte) { - // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 - sfm := getServerFdMap() - p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - // 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理,以便子进程获取到正确的fd - for name, m := range sfm { - for fdk, fdv := range m { - if len(fdv) > 0 { - s := "" - for _, item := range strings.Split(fdv, ",") { - array := strings.Split(item, "#") - fd := uintptr(gconv.Uint(array[1])) - s += fmt.Sprintf("%s#%d,", array[0], len(p.GetAttr().Files)) - p.GetAttr().Files = append(p.GetAttr().Files, os.NewFile(fd, "")) + var buffer []byte = nil + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + // windows系统无法进行文件描述符操作,只能重启进程 + if runtime.GOOS == "windows" { + closeWebServers() + } else { + // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 + sfm := getServerFdMap() + // 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理,以便子进程获取到正确的fd + for name, m := range sfm { + for fdk, fdv := range m { + if len(fdv) > 0 { + s := "" + for _, item := range strings.Split(fdv, ",") { + array := strings.Split(item, "#") + fd := uintptr(gconv.Uint(array[1])) + s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles)) + p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, "")) + } + sfm[name][fdk] = strings.TrimRight(s, ",") } - sfm[name][fdk] = strings.TrimRight(s, ",") } } + buffer, _ = gjson.Encode(sfm) + } + p.PPid = gproc.PPid() + if newPid, err := p.Start(); err == nil { + sendProcessMsg(newPid, gMSG_START, buffer) + } else { + glog.Errorfln("%d: fork process failed, error:%s", err.Error()) } - p.SetPpid(gproc.Ppid()) - p.Run() - // 编码,通信 - b, _ := gjson.Encode(sfm) - sendProcessMsg(p.Pid(), gMSG_START, b) } // 友好关闭服务链接并退出 func onCommChildShutdown(pid int, data []byte) { - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - for _, s := range v.(*Server).servers { - s.shutdown() - } - } - }) - sendProcessMsg(gproc.Ppid(), gMSG_REMOVE_PROC, gbinary.EncodeInt(gproc.Pid())) + if runtime.GOOS != "windows" { + shutdownWebServers() + } + sendProcessMsg(gproc.PPid(), gMSG_REMOVE_PROC, nil) } // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 func handleChildProcessHeartbeat() { for { time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) - sendProcessMsg(gproc.Ppid(), gMSG_HEARTBEAT, nil) + sendProcessMsg(gproc.PPid(), gMSG_HEARTBEAT, nil) // 超过时间没有接收到主进程心跳,自动关闭退出 if heartbeatStarted.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return - glog.Errorfln("%d heartbeat timeout, exit", gproc.Pid()) + glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 4ff63b8cc..344343f92 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -12,7 +12,6 @@ import ( "time" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gmap" - "gitee.com/johng/gf/g/encoding/gbinary" ) // (主进程)主进程与子进程上一次活跃时间映射map @@ -21,7 +20,7 @@ var procUpdateMap = gmap.NewIntIntMap() // 开启服务 func onCommMainStart(pid int, data []byte) { p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - p.Run() + p.Start() sendProcessMsg(p.Pid(), gMSG_START, nil) } @@ -44,7 +43,7 @@ func onCommMainNewFork(pid int, data []byte) { // 销毁子进程通知 func onCommMainRemoveProc(pid int, data []byte) { - procManager.RemoveProcess(gbinary.DecodeToInt(data)) + procManager.RemoveProcess(pid) } // 关闭服务,通知所有子进程退出 @@ -69,7 +68,6 @@ func handleMainProcessHeartbeat() { // 这里需要手动从进程管理器中去掉该进程 procManager.RemoveProcess(pid) sendProcessMsg(pid, gMSG_SHUTDOWN, nil) - return } } } diff --git a/g/net/ghttp/ghttp_server_comm_signal_unix.go b/g/net/ghttp/ghttp_server_comm_signal_unix.go new file mode 100644 index 000000000..177ba6067 --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_signal_unix.go @@ -0,0 +1,45 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +// +build !windows + +package ghttp + +import ( + "os" + "syscall" + "os/signal" + "gitee.com/johng/gf/g/os/gproc" +) + +// 信号量处理 +func handleProcessSignal() { + var sig os.Signal + signal.Notify( + procSignalChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGHUP, + syscall.SIGTERM, + syscall.SIGUSR1, + ) + for { + sig = <- procSignalChan + switch sig { + // 进程终止,停止所有子进程运行 + case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + return + + // 用户信号,重启服务 + case syscall.SIGUSR1: + sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) + + default: + } + } +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_signal_windows.go b/g/net/ghttp/ghttp_server_comm_signal_windows.go new file mode 100644 index 000000000..148375961 --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_signal_windows.go @@ -0,0 +1,12 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package ghttp + +// windows不处理信号量 +func handleProcessSignal() { + +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 5558c3fec..b2eded2f7 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -141,3 +141,13 @@ func (s *gracefulServer) shutdown() { } } +// 执行请求强制关闭 +func (s *gracefulServer) close() { + if err := s.httpServer.Close(); err != nil { + glog.Errorfln("%d: %s server [%s] close error: %v", gproc.Pid(), s.getProto(), s.addr, err) + } else { + glog.Printfln("%d: %s server [%s] close smoothly", gproc.Pid(), s.getProto(), s.addr) + s.shutdownChan <- true + } +} + diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go index 353be58fb..74d172801 100644 --- a/g/os/gproc/gproc.go +++ b/g/os/gproc/gproc.go @@ -4,7 +4,9 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://gitee.com/johng/gf. -// 进程管理. +// 进程管理/通信. +// 本进程管理从syscall, os.StartProcess, exec.Cmd都使用过, +// 最后采用了exec.Cmd来实现多进程管理,这是一个顶层的跨平台封装,兼容性更好,另外两个是偏底层的接口。 package gproc import ( @@ -22,7 +24,7 @@ func Pid() int { } // 获取父进程ID(gproc父进程,不存在时则使用系统父进程) -func Ppid() int { +func PPid() int { // gPROC_ENV_KEY_PPID_KEY为gproc包自定义的父进程 ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) if ppidValue != "" { diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 6f1632851..518f407d9 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -39,14 +39,20 @@ type Msg struct { func init() { path := getCommFilePath(os.Getpid()) if !gfile.Exists(path) { + // 检测存在性 if err := gfile.Create(path); err != nil { glog.Error(err) + os.Exit(1) } - } else { - // 初始化时读取已有数据(文件修改时间在10秒以内) - if gtime.Second() - gfile.MTime(path) < 10 { - checkCommBuffer(path) - } + } + // 检测写入权限 + if !gfile.IsWritable(path) { + glog.Errorfln("%s is not writable for gproc", path) + os.Exit(1) + } + // 初始化时读取已有数据(文件修改时间在10秒以内) + if gtime.Second() - gfile.MTime(path) < 10 { + checkCommBuffer(path) } // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 err := gfsnotify.Add(path, func(event *gfsnotify.Event) { diff --git a/g/os/gproc/gproc_manager.go b/g/os/gproc/gproc_manager.go index 1e3119682..8fb8a7ea9 100644 --- a/g/os/gproc/gproc_manager.go +++ b/g/os/gproc/gproc_manager.go @@ -10,8 +10,9 @@ package gproc import ( "os" "fmt" - "gitee.com/johng/gf/g/container/gmap" "strings" + "os/exec" + "gitee.com/johng/gf/g/container/gmap" ) const ( @@ -32,29 +33,45 @@ func NewManager() *Manager { // 创建一个进程(不执行) func NewProcess(path string, args []string, environment []string) *Process { - env := make([]string, len(environment) + 2) + env := make([]string, len(environment) + 1) for k, v := range environment { env[k] = v } env[len(env) - 1] = fmt.Sprintf("%s=%s", gPROC_TEMP_DIR_ENV_KEY, os.TempDir()) p := &Process { - path : path, - args : make([]string, 0), - ppid : os.Getpid(), - attr : &os.ProcAttr { - Env : env, - Files : []*os.File{ os.Stdin,os.Stdout,os.Stderr }, + Manager : nil, + PPid : os.Getpid(), + Cmd : exec.Cmd { + Args : []string{path}, + Path : path, + Stdin : os.Stdin, + Stdout : os.Stdout, + Stderr : os.Stderr, + Env : env, + ExtraFiles : make([]*os.File, 0), }, } - if len(args) > 0 { - p.args = append(p.args, args[0]) + // 当前工作目录 + if d, err := os.Getwd(); err == nil { + p.Dir = d } // 判断是否加上子进程标识 - if len(args) == 1 || (len(args) > 1 && !strings.EqualFold(args[1], gCHILD_ARGS_MARK_NAME)) { - p.args = append(p.args, gCHILD_ARGS_MARK_NAME) + hasChildMark := false + childMarkLen := len(gCHILD_ARGS_MARK_NAME) + for _, v := range args { + if len(v) >= childMarkLen && strings.EqualFold(v[0 : childMarkLen], gCHILD_ARGS_MARK_NAME) { + hasChildMark = true + } } - if len(args) > 1 { - p.args = append(p.args, args[1 : ]...) + if !hasChildMark { + p.Args = append(p.Args, gCHILD_ARGS_MARK_NAME) + } + if len(args) > 0 { + start := 0 + if strings.EqualFold(path, args[0]) { + start = 1 + } + p.Args = append(p.Args, args[start : ]...) } return p } @@ -62,7 +79,7 @@ func NewProcess(path string, args []string, environment []string) *Process { // 创建一个进程(不执行) func (m *Manager) NewProcess(path string, args []string, environment []string) *Process { p := NewProcess(path, args, environment) - p.SetManager(m) + p.Manager = m return p } @@ -78,7 +95,7 @@ func (m *Manager) GetProcess(pid int) *Process { func (m *Manager) AddProcess(pid int) { if process, err := os.FindProcess(pid); err == nil { p := m.NewProcess("", nil, nil) - p.process = process + p.Process = process m.processes.Set(pid, p) } } diff --git a/g/os/gproc/gproc_proccess.go b/g/os/gproc/gproc_proccess.go index c414335d0..3ee7a224f 100644 --- a/g/os/gproc/gproc_proccess.go +++ b/g/os/gproc/gproc_proccess.go @@ -10,113 +10,55 @@ import ( "os" "fmt" "errors" + "os/exec" ) // 子进程 type Process struct { - pm *Manager // 所属进程管理器 - path string // 可执行文件绝对路径 - args []string // 执行参数 - attr *os.ProcAttr // 进程属性 - ppid int // 自定义关联的父进程ID - process *os.Process // 底层进程对象 + exec.Cmd + Manager *Manager // 所属进程管理器 + PPid int // 自定义关联的父进程ID } -// 运行进程 -func (p *Process) Run() (int, error) { - if p.process != nil { +// 开始执行(非阻塞) +func (p *Process) Start() (int, error) { + if p.Process != nil { return p.Pid(), nil } - if p.ppid > 0 { - p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) + if p.PPid > 0 { + p.Env = append(p.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.PPid)) } - if process, err := os.StartProcess(p.path, p.args, p.attr); err == nil { - p.process = process - if p.pm != nil { - p.pm.processes.Set(process.Pid, p) + if err := p.Cmd.Start(); err == nil { + if p.Manager != nil { + p.Manager.processes.Set(p.Process.Pid, p) } - return process.Pid, nil + return p.Process.Pid, nil } else { return 0, err } } -// 运行进程,守护进程,主进程退出后不退出 -//func (p *Process) RunDaemon() (int, error) { -// if p.process != nil { -// return p.Pid(), nil -// } -// p.attr.Env = append(p.attr.Env, fmt.Sprintf("%s=%d", gPROC_ENV_KEY_PPID_KEY, p.ppid)) -// procAttr := &syscall.ProcAttr { -// Dir : p.attr.Dir, -// Env : p.attr.Env, -// Sys : p.attr.Sys, -// Files : make([]uintptr, 0), -// } -// // 将os.ProcAttr.Files转换为底层的syscall.ProcAttr.Files -// for _, f := range p.attr.Files { -// procAttr.Files = append(procAttr.Files, f.Fd()) -// } -// if pid, err := syscall.ForkExec(p.path, p.args, procAttr); err == nil { -// p.process, _ = os.FindProcess(pid) -// if p.pm != nil { -// p.pm.processes.Set(pid, p) -// } -// return pid, nil -// } else { -// return 0, err -// } -//} - -func (p *Process) SetManager(m *Manager) { - p.pm = m -} - -// 设置自定义的父进程ID -func (p *Process) SetPpid(ppid int) { - p.ppid = ppid -} - -func (p *Process) SetArgs(args []string) { - p.args = args -} - -func (p *Process) AddArgs(args []string) { - for _, v := range args { - p.args = append(p.args, v) +// 运行进程(阻塞等待执行完毕) +func (p *Process) Run() error { + if _, err := p.Start(); err == nil { + return p.Wait() + } else { + return err } } -func (p *Process) SetEnv(env []string) { - p.attr.Env = env -} - -func (p *Process) AddEnv(env []string) { - for _, v := range env { - p.attr.Env = append(p.attr.Env, v) - } -} - -func (p *Process) SetAttr(attr *os.ProcAttr) { - p.attr = attr -} - -func (p *Process) GetAttr() *os.ProcAttr { - return p.attr -} - // PID func (p *Process) Pid() int { - if p.process != nil { - return p.process.Pid + if p.Process != nil { + return p.Process.Pid } return 0 } // 向进程发送消息 func (p *Process) Send(data interface{}) error { - if p.process != nil { - return Send(p.process.Pid, data) + if p.Process != nil { + return Send(p.Process.Pid, data) } return errors.New("process not running") } @@ -126,14 +68,14 @@ func (p *Process) Send(data interface{}) error { // rendering it unusable in the future. // Release only needs to be called if Wait is not. func (p *Process) Release() error { - return p.process.Release() + return p.Process.Release() } // Kill causes the Process to exit immediately. func (p *Process) Kill() error { - if err := p.process.Kill(); err == nil { - if p.pm != nil { - p.pm.processes.Remove(p.Pid()) + if err := p.Process.Kill(); err == nil { + if p.Manager != nil { + p.Manager.processes.Remove(p.Pid()) } return nil } else { @@ -141,17 +83,8 @@ func (p *Process) Kill() error { } } -// Wait waits for the Process to exit, and then returns a -// ProcessState describing its status and an error, if any. -// Wait releases any resources associated with the Process. -// On most operating systems, the Process must be a child -// of the current process or an error will be returned. -func (p *Process) Wait() (*os.ProcessState, error) { - return p.process.Wait() -} - // Signal sends a signal to the Process. // Sending Interrupt on Windows is not implemented. func (p *Process) Signal(sig os.Signal) error { - return p.process.Signal(sig) + return p.Process.Signal(sig) } \ No newline at end of file diff --git a/geg/os/gproc/cmd.go b/geg/os/gproc/cmd.go new file mode 100644 index 000000000..0cd84127f --- /dev/null +++ b/geg/os/gproc/cmd.go @@ -0,0 +1,15 @@ +package main + +import ( + "os" + "fmt" + "time" + "os/exec" +) + +func main () { + cmd := exec.Command(os.Args[0], "1") + time.Sleep(3*time.Second) + fmt.Println(cmd.Start()) + time.Sleep(time.Hour) +} diff --git a/geg/os/gproc/gproc.go b/geg/os/gproc/gproc.go index 85332d7ae..7761ef34a 100644 --- a/geg/os/gproc/gproc.go +++ b/geg/os/gproc/gproc.go @@ -9,18 +9,17 @@ import ( ) func main () { + fmt.Printf("%d: I am child? %v\n", gproc.Pid(), gproc.IsChild()) if gproc.IsChild() { - fmt.Printf(" child pid is %d\n", os.Getpid()) gtime.SetInterval(time.Second, func() bool { - gproc.Send(gproc.Ppid(), gtime.Datetime()) + gproc.Send(gproc.PPid(), gtime.Datetime()) return true }) select { } } else { - fmt.Printf("parent pid is %d\n", os.Getpid()) - m := gproc.New() - p := m.NewProcess(os.Args[0], os.Args, nil) - p.Run() + m := gproc.NewManager() + p := m.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Start() for { msg := gproc.Receive() fmt.Printf("receive from %d, data: %s\n", msg.Pid, string(msg.Data)) From 85020dfcc2fdb35b3ec4cc8cd2cb4a421ad84509 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 May 2018 14:04:37 +0800 Subject: [PATCH 17/31] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90ghttp?= =?UTF-8?q?.Server=E5=9C=A8Linux=E5=8F=8AWindows=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E5=8A=9F=E8=83=BD=E5=8F=8A=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/g.go | 7 ++- g/net/ghttp/ghttp_server.go | 62 ++++++++++--------- g/net/ghttp/ghttp_server_comm.go | 21 ++++--- g/net/ghttp/ghttp_server_comm_child.go | 51 +++++++-------- g/net/ghttp/ghttp_server_comm_child_unix.go | 39 ++++++++++++ .../ghttp/ghttp_server_comm_child_windows.go | 35 +++++++++++ g/net/ghttp/ghttp_server_comm_main.go | 40 ++++++++++-- g/net/ghttp/ghttp_server_graceful.go | 4 +- g/os/gproc/gproc.go | 30 ++++++++- .../hot_restart/multi_port_and_server.go | 6 +- geg/other/test.go | 2 +- 11 files changed, 217 insertions(+), 80 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_comm_child_unix.go create mode 100644 g/net/ghttp/ghttp_server_comm_child_windows.go diff --git a/g/g.go b/g/g.go index 961214a69..8f399bca6 100644 --- a/g/g.go +++ b/g/g.go @@ -32,6 +32,12 @@ type Map map[string]interface{} // 常用list数据结构 type List []Map + +// 阻塞等待HTTPServer执行完成(同一进程多HTTPServer情况下) +func Wait() { + ghttp.Wait() +} + // HTTPServer单例对象 func Server(name...interface{}) *ghttp.Server { return ghttp.GetServer(name...) @@ -58,7 +64,6 @@ func Config() *gcfg.Config { return gins.Config() } - // 数据库操作对象,使用了连接池 func Database(name...string) *gdb.Db { config := gins.Config() diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 791b14bf9..64cfc481a 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -13,6 +13,7 @@ import ( "errors" "strings" "reflect" + "runtime" "net/http" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gproc" @@ -21,7 +22,6 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" - "runtime" ) const ( @@ -108,40 +108,31 @@ var serverMapping = gmap.NewStringInterfaceMap() var procManager = gproc.NewManager() // Web Server开始执行事件通道,由于同一个进程支持多Server,因此该通道为非阻塞 -var runChan = make(chan struct{}, 100000) -// 已完成的run消息队列 +var readyChan = make(chan struct{}, 100000) +// Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出 var doneChan = make(chan struct{}, 100000) -// 阻塞消息队列,用于ghttp.Wait -var waitChan = make(chan struct{}, 0) // Web Server进程初始化 func init() { go func() { - // 等待run消息(Run方法调用) - <- runChan + // 等待ready消息(Run方法调用) + <- readyChan // 主进程只负责创建子进程 if !gproc.IsChild() { sendProcessMsg(os.Getpid(), gMSG_START, nil) } // 开启进程消息监听处理 handleProcessMsgAndSignal() + // 服务执行完成,需要退出 doneChan <- struct{}{} if !gproc.IsChild() { glog.Printfln("%d: all web server shutdown smoothly", gproc.Pid()) } - - // 停止进程等待 - close(waitChan) }() } -// 阻塞等待所有Web Server停止,常用于多Web Server场景,以及需要将Web Server异步运行的场景 -func Wait() { - <- waitChan -} - // 获取/创建一个默认配置的HTTP Server(默认监听端口是80) // 单例模式,请保证name的唯一性 func GetServer(name...interface{}) (*Server) { @@ -190,32 +181,47 @@ func GetServer(name...interface{}) (*Server) { return s } -// 阻塞执行监听 -func (s *Server) Run() error { - runChan <- struct{}{} - +// 作为守护协程异步执行(当同一进程中存在多个Web Server时,需要采用这种方式执行) +// 需要结合Wait方式一起使用 +func (s *Server) Start() error { + // 主进程,不执行任何业务,只负责进程管理 if !gproc.IsChild() { - <- doneChan return nil } if s.status == 1 { return errors.New("server is already running") } - // 底层http server配置 if s.config.Handler == nil { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) } - // 开启异步关闭队列处理循环 s.startCloseQueueLoop() + return nil +} +// 阻塞执行监听 +func (s *Server) Run() error { + if err := s.Start(); err != nil { + return err + } + // Web Server准备就绪,待执行 + readyChan <- struct{}{} // 阻塞等待服务执行完成 <- doneChan return nil } + +// 阻塞等待所有Web Server停止,常用于多Web Server场景,以及需要将Web Server异步运行的场景 +// 这是一个与进程相关的方法 +func Wait() { + readyChan <- struct{}{} + <- doneChan +} + + // 开启底层Web Server执行 func (s *Server) startServer(fdMap listenerFdMap) { // 开始执行底层Web Server创建,端口监听 @@ -239,13 +245,13 @@ func (s *Server) startServer(fdMap listenerFdMap) { } for _, v := range array { - go func(item string) { + go func(addrItem string) { // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 if isFd && runtime.GOOS != "windows" { - tArray := strings.Split(item, "#") + tArray := strings.Split(addrItem, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { - server = s.newGracefulServer(item) + server = s.newGracefulServer(addrItem) } s.servers = append(s.servers, server) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { @@ -271,13 +277,13 @@ func (s *Server) startServer(fdMap listenerFdMap) { array = strings.Split(s.config.Addr, ",") } for _, v := range array { - go func(item string) { + go func(addrItem string) { // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 if isFd && runtime.GOOS != "windows" { - tArray := strings.Split(item, "#") + tArray := strings.Split(addrItem, "#") server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) } else { - server = s.newGracefulServer(item) + server = s.newGracefulServer(addrItem) } s.servers = append(s.servers, server) if err := server.ListenAndServe(); err != nil { diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 7b410f968..a2fa3b527 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -15,7 +15,6 @@ import ( "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" - "fmt" ) const ( @@ -26,15 +25,17 @@ const ( gMSG_REMOVE_PROC = 50 gMSG_HEARTBEAT = 60 - gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 - gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 + gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 + gPROC_HEARTBEAT_TIMEOUT = 5000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 + gPROC_MULTI_CHILD_CLEAR_INTERVAL = 1000 // (毫秒)检测间隔,当存在多个子进程时(往往是重启间隔非常短且频繁造成),需要进行清理,最终留下一个最新的子进程 + gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 5000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 ) // 进程信号量监听消息队列 -var procSignalChan = make(chan os.Signal) +var procSignalChan = make(chan os.Signal) // (主子进程)在第一次创建子进程成功之后才会开始心跳检测,同理对应超时时间才会生效 -var heartbeatStarted = gtype.NewBool() +var checkHeartbeat = gtype.NewBool() // 处理进程信号量监控以及进程间消息通信 func handleProcessMsgAndSignal() { @@ -43,6 +44,7 @@ func handleProcessMsgAndSignal() { go handleChildProcessHeartbeat() } else { go handleMainProcessHeartbeat() + go handleMainProcessChildClear() } handleProcessMsg() } @@ -53,8 +55,8 @@ func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { // 记录消息日志,用于调试 - content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) - fmt.Print(content) + //content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) + //fmt.Print(content) //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] @@ -64,7 +66,7 @@ func handleProcessMsg() { // =============== // 任何与父进程的通信都会更新最后通信时间 if msg.Pid == gproc.PPid() { - lastHeartbeatTime.Set(int(gtime.Millisecond())) + updateProcessChildUpdateTime() } switch act { case gMSG_START: onCommChildStart(msg.Pid, data) @@ -82,6 +84,9 @@ func handleProcessMsg() { if msg.Pid != gproc.Pid() { updateProcessCommTime(msg.Pid) } + if !procFirstTimeMap.Contains(msg.Pid) { + procFirstTimeMap.Set(msg.Pid, int(gtime.Millisecond())) + } switch act { case gMSG_START: onCommMainStart(msg.Pid, data) case gMSG_RESTART: onCommMainRestart(msg.Pid, data) diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index cffa5dbf9..fb0a5dfec 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -21,37 +21,16 @@ import ( "gitee.com/johng/gf/g/container/gtype" ) +const ( + gPROC_CHILD_MAX_IDLE_TIME = 3000 // 子进程闲置时间(未开启心跳机制的时间) +) + // (子进程)上一次从主进程接收心跳的时间戳 var lastHeartbeatTime = gtype.NewInt() -// 开启所有Web Server(根据消息启动) -func onCommChildStart(pid int, data []byte) { - if len(data) > 0 { - sfm := bufferToServerFdMap(data) - for k, v := range sfm { - GetServer(k).startServer(v) - } - } else { - serverMapping.RLockFunc(func(m map[string]interface{}) { - for _, v := range m { - v.(*Server).startServer(nil) - } - }) - } - // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 - sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) - // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程销毁 - if os.Getppid() != gproc.PPid() { - //glog.Printfln("%d: ask os.ppid %d to exit, proc.ppid:%d", gproc.Pid(), os.Getppid(), gproc.PPid()) - sendProcessMsg(os.Getppid(), gMSG_SHUTDOWN, nil) - } - heartbeatStarted.Set(true) -} - // 心跳消息 func onCommChildHeartbeat(pid int, data []byte) { - //glog.Printfln("%d: update heartbeat", gproc.Pid()) - lastHeartbeatTime.Set(int(gtime.Millisecond())) + updateProcessChildUpdateTime() } // 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 @@ -60,6 +39,7 @@ func onCommChildRestart(pid int, data []byte) { p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) // windows系统无法进行文件描述符操作,只能重启进程 if runtime.GOOS == "windows" { + // windows下使用shutdownWebServers会造成协程阻塞,这里直接使用close强制关闭 closeWebServers() } else { // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 @@ -89,12 +69,17 @@ func onCommChildRestart(pid int, data []byte) { } } -// 友好关闭服务链接并退出 +// 关闭服务链接并退出 func onCommChildShutdown(pid int, data []byte) { + sendProcessMsg(gproc.PPid(), gMSG_REMOVE_PROC, nil) if runtime.GOOS != "windows" { shutdownWebServers() } - sendProcessMsg(gproc.PPid(), gMSG_REMOVE_PROC, nil) +} + +// 更新上一次主进程主动与子进程通信的时间 +func updateProcessChildUpdateTime() { + lastHeartbeatTime.Set(int(gtime.Millisecond())) } // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 @@ -103,10 +88,16 @@ func handleChildProcessHeartbeat() { time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) sendProcessMsg(gproc.PPid(), gMSG_HEARTBEAT, nil) // 超过时间没有接收到主进程心跳,自动关闭退出 - if heartbeatStarted.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { + if checkHeartbeat.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return - glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) + //glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastHeartbeatTime.Val(), gPROC_HEARTBEAT_TIMEOUT) + //glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) + os.Exit(0) + } + // 未开启心跳检测的闲置超过一定时间则主动关闭 + if !checkHeartbeat.Val() && gproc.Uptime() > gPROC_CHILD_MAX_IDLE_TIME { + //glog.Printfln("%d: max idle time exceeded, exit", gproc.Pid()) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_child_unix.go b/g/net/ghttp/ghttp_server_comm_child_unix.go new file mode 100644 index 000000000..f112dd55f --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_child_unix.go @@ -0,0 +1,39 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. +// Web Server进程间通信 - 子进程 + +// +build !windows + +package ghttp + +import ( + "gitee.com/johng/gf/g/os/gproc" +) + +// 开启所有Web Server(根据消息启动) +func onCommChildStart(pid int, data []byte) { + if len(data) > 0 { + sfm := bufferToServerFdMap(data) + for k, v := range sfm { + GetServer(k).startServer(v) + } + } else { + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Server).startServer(nil) + } + }) + } + // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 + sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) + // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 + if gproc.PPidOS() != gproc.PPid() { + sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) + } + // 开始心跳时必须保证主进程时间有值,但是又不能等待主进程消息后再开始检测,因此这里自己更新一下通信时间 + updateProcessChildUpdateTime() + checkHeartbeat.Set(true) +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_child_windows.go b/g/net/ghttp/ghttp_server_comm_child_windows.go new file mode 100644 index 000000000..54a0455a4 --- /dev/null +++ b/g/net/ghttp/ghttp_server_comm_child_windows.go @@ -0,0 +1,35 @@ +// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package ghttp + +import ( + "os" + "gitee.com/johng/gf/g/os/gproc" +) + +// 开启所有Web Server(根据消息启动) +func onCommChildStart(pid int, data []byte) { + // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 + sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) + // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 + if gproc.PPidOS() != gproc.PPid() { + sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) + // 在windows下必须等待父进程销毁后才能表明Server资源已被释放,才能开始端口监听,否则会端口资源冲突 + if p, err := os.FindProcess(gproc.PPidOS()); err == nil { + p.Wait() + } + } + // 开启Web Server服务 + serverMapping.RLockFunc(func(m map[string]interface{}) { + for _, v := range m { + v.(*Server).startServer(nil) + } + }) + // 开始心跳时必须保证主进程时间有值,但是又不能等待主进程消息后再开始检测,因此这里自己更新一下通信时间 + updateProcessChildUpdateTime() + checkHeartbeat.Set(true) +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 344343f92..ebabf2e01 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -3,19 +3,26 @@ // 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://gitee.com/johng/gf. -// Web Server进程间通信 - 主进程 +// Web Server进程间通信 - 主进程. +// 管理子进程按照规则听话玩,不听话有一百种方法让子进程在本地混不下去. package ghttp import ( "os" "time" + "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gmap" ) +// (主进程)子进程与主进程第一次通信的时间映射map +// 用以识别子进程创建时间先后顺序,当存在多个子进程时,主动销毁旧的子进程 +var procFirstTimeMap = gmap.NewIntIntMap() + // (主进程)主进程与子进程上一次活跃时间映射map -var procUpdateMap = gmap.NewIntIntMap() +var procUpdateTimeMap = gmap.NewIntIntMap() // 开启服务 func onCommMainStart(pid int, data []byte) { @@ -38,7 +45,7 @@ func onCommMainRestart(pid int, data []byte) { // 新建子进程通知 func onCommMainNewFork(pid int, data []byte) { procManager.AddProcess(pid) - heartbeatStarted.Set(true) + checkHeartbeat.Set(true) } // 销毁子进程通知 @@ -53,7 +60,7 @@ func onCommMainShutdown(pid int, data []byte) { // 更新指定进程的通信时间记录 func updateProcessCommTime(pid int) { - procUpdateMap.Set(pid, int(gtime.Millisecond())) + procUpdateTimeMap.Set(pid, int(gtime.Millisecond())) } // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 @@ -62,9 +69,9 @@ func handleMainProcessHeartbeat() { time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) procManager.Send(formatMsgBuffer(gMSG_HEARTBEAT, nil)) // 清理过期进程 - if heartbeatStarted.Val() { + if checkHeartbeat.Val() { for _, pid := range procManager.Pids() { - if int(gtime.Millisecond()) - procUpdateMap.Get(pid) > gPROC_HEARTBEAT_TIMEOUT { + if int(gtime.Millisecond()) - procUpdateTimeMap.Get(pid) > gPROC_HEARTBEAT_TIMEOUT { // 这里需要手动从进程管理器中去掉该进程 procManager.RemoveProcess(pid) sendProcessMsg(pid, gMSG_SHUTDOWN, nil) @@ -72,4 +79,25 @@ func handleMainProcessHeartbeat() { } } } +} + +// 清理多余的子进程 +func handleMainProcessChildClear() { + for { + time.Sleep(gPROC_MULTI_CHILD_CLEAR_INTERVAL*time.Millisecond) + if procManager.Size() > 1 { + minPid := 0 + minTime := int(gtime.Millisecond()) + for _, pid := range procManager.Pids() { + if t := procFirstTimeMap.Get(pid); t < minTime { + minPid = pid + minTime = t + } + } + if minPid > 0 && procUpdateTimeMap.Get(minPid) - procFirstTimeMap.Get(minPid) > gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE { + sendProcessMsg(minPid, gMSG_SHUTDOWN, nil) + glog.Printfln("%d: multi child occurred, shutdown %d", gproc.Pid(), minPid) + } + } + } } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index b2eded2f7..cf7a3cabc 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -144,9 +144,9 @@ func (s *gracefulServer) shutdown() { // 执行请求强制关闭 func (s *gracefulServer) close() { if err := s.httpServer.Close(); err != nil { - glog.Errorfln("%d: %s server [%s] close error: %v", gproc.Pid(), s.getProto(), s.addr, err) + glog.Errorfln("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - glog.Printfln("%d: %s server [%s] close smoothly", gproc.Pid(), s.getProto(), s.addr) + glog.Printfln("%d: %s server [%s] closed smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go index 74d172801..3b6deaa9b 100644 --- a/g/os/gproc/gproc.go +++ b/g/os/gproc/gproc.go @@ -11,13 +11,21 @@ package gproc import ( "os" + "time" "gitee.com/johng/gf/g/util/gconv" + "gitee.com/johng/gf/g/container/gtype" ) const ( gPROC_ENV_KEY_PPID_KEY = "gproc.ppid" ) +// 进程开始执行时间 +var processStartTime = time.Now() + +// 优雅退出标识符号 +var isExited = gtype.NewBool() + // 获取当前进程ID func Pid() int { return os.Getpid() @@ -34,7 +42,7 @@ func PPid() int { } // 获取父进程ID(系统父进程) -func PpidOfOs() int { +func PPidOS() int { return os.Getppid() } @@ -43,3 +51,23 @@ func IsChild() bool { return os.Getenv(gPROC_ENV_KEY_PPID_KEY) != "" } +// 进程开始执行时间 +func StartTime() time.Time { + return processStartTime +} + +// 进程已经运行的时间(毫秒) +func Uptime() int { + return int(time.Now().UnixNano()/1e6 - processStartTime.UnixNano()/1e6) +} + +// 标识当前进程为退出状态,其他业务可以根据此标识来执行优雅退出 +func SetExited() { + isExited.Set(true) +} + +// 当前进程是否被标识为退出状态 +func Exited() bool { + return isExited.Val() +} + diff --git a/geg/net/ghttp/hot_restart/multi_port_and_server.go b/geg/net/ghttp/hot_restart/multi_port_and_server.go index f7dbfac35..0dd6e39d9 100644 --- a/geg/net/ghttp/hot_restart/multi_port_and_server.go +++ b/geg/net/ghttp/hot_restart/multi_port_and_server.go @@ -19,14 +19,14 @@ func main() { r.Server.Shutdown() }) s1.SetPort(8199, 8200) - go s1.Run() + s1.Start() s2 := g.Server("s2") s2.BindHandler("/", func(r *ghttp.Request){ r.Response.Writeln("hello s2") }) s2.SetPort(8300, 8080) - go s2.Run() + s2.Start() - ghttp.Wait() + g.Wait() } \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index ce225f777..2af9ff714 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -43,7 +43,7 @@ func checksum(buffer []byte) uint32 { } func main(){ - b := gfile.GetBinContents("/tmp/gproc/27501") + b := gfile.GetBinContents("/tmp/gproc/30588") for _, msg := range bufferToMsgs(b) { fmt.Println(msg.Pid) fmt.Println(msg.Data) From d8bd344b66e9ccf0ea3f344d2c31a8dfae8c1d2e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 May 2018 14:23:14 +0800 Subject: [PATCH 18/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E7=89=B9=E6=80=A7=E5=BC=80=E5=8F=91=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E6=B5=8B=E8=AF=95=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_admin.go | 57 +++++++++++++++++++++++++++++ g/net/ghttp/ghttp_server_auto.go | 25 ------------- g/net/ghttp/ghttp_server_handler.go | 19 +++++++++- g/net/ghttp/ghttp_server_pprof.go | 19 +++++----- geg/net/ghttp/hot_restart/admin.go | 12 ++++++ 5 files changed, 96 insertions(+), 36 deletions(-) create mode 100644 g/net/ghttp/ghttp_server_admin.go delete mode 100644 g/net/ghttp/ghttp_server_auto.go create mode 100644 geg/net/ghttp/hot_restart/admin.go diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go new file mode 100644 index 000000000..3f5778e68 --- /dev/null +++ b/g/net/ghttp/ghttp_server_admin.go @@ -0,0 +1,57 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. +// pprof封装. + +package ghttp + +import ( + "strings" + "gitee.com/johng/gf/g/os/gview" +) + +// 用于服务管理的对象 +type utilAdmin struct {} + +// 服务管理首页 +func (p *utilAdmin) Index(r *Request) { + data := map[string]interface{}{ + "uri" : strings.TrimRight(r.URL.Path, "/"), + } + buffer, _ := gview.ParseContent(` + + + gf ghttp admin + + +

restart

+

shutdown

+ + + `, data) + r.Response.Write(buffer) +} + +// 服务重启 +func (p *utilAdmin) Restart(r *Request) { + r.Response.Write("restart server") + r.Server.Restart() +} + +// 服务关闭 +func (p *utilAdmin) Shutdown(r *Request) { + r.Response.Write("shutdown server") + r.Server.Shutdown() +} + + +// 开启服务管理支持 +func (s *Server) EnableAdmin(pattern...string) { + p := "/debug/admin" + if len(pattern) > 0 { + p = pattern[0] + } + s.BindObject(p, &utilAdmin{}) +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_auto.go b/g/net/ghttp/ghttp_server_auto.go deleted file mode 100644 index f889b1ed3..000000000 --- a/g/net/ghttp/ghttp_server_auto.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. -// -// 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://gitee.com/johng/gf. -// 异步工作协程. - -package ghttp - -// 开启异步队列处理循环,该异步线程与Server同生命周期 -func (s *Server) startCloseQueueLoop() { - go func() { - for { - if v := s.closeQueue.PopFront(); v != nil { - r := v.(*Request) - s.callHookHandler(r, "BeforeClose") - // 关闭当前会话的Cookie - r.Cookie.Close() - // 更新Session会话超时时间 - r.Session.UpdateExpire() - s.callHookHandler(r, "AfterClose") - } - } - }() -} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_handler.go b/g/net/ghttp/ghttp_server_handler.go index 9550fc9cb..1d10478cc 100644 --- a/g/net/ghttp/ghttp_server_handler.go +++ b/g/net/ghttp/ghttp_server_handler.go @@ -16,8 +16,8 @@ import ( "net/url" "net/http" "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/encoding/ghtml" "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/encoding/ghtml" ) // 默认HTTP Server处理入口,http包底层默认使用了gorutine异步处理请求,所以这里不再异步执行 @@ -169,3 +169,20 @@ func (s *Server)listDir(r *Request, f http.File) { } r.Response.Write("\n") } + +// 开启异步队列处理循环,该异步线程与Server同生命周期 +func (s *Server) startCloseQueueLoop() { + go func() { + for { + if v := s.closeQueue.PopFront(); v != nil { + r := v.(*Request) + s.callHookHandler(r, "BeforeClose") + // 关闭当前会话的Cookie + r.Cookie.Close() + // 更新Session会话超时时间 + r.Session.UpdateExpire() + s.callHookHandler(r, "AfterClose") + } + } + }() +} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_pprof.go b/g/net/ghttp/ghttp_server_pprof.go index b5043394e..0205a0b3a 100644 --- a/g/net/ghttp/ghttp_server_pprof.go +++ b/g/net/ghttp/ghttp_server_pprof.go @@ -11,13 +11,13 @@ import ( "strings" runpprof "runtime/pprof" netpprof "net/http/pprof" - "gitee.com/johng/gf/g/frame/gins" + "gitee.com/johng/gf/g/os/gview" ) // 用于pprof的对象 -type utilpprof struct {} +type utilPprof struct {} -func (p *utilpprof) Index(r *Request) { +func (p *utilPprof) Index(r *Request) { profiles := runpprof.Profiles() action := r.Get("action") data := map[string]interface{}{ @@ -25,8 +25,7 @@ func (p *utilpprof) Index(r *Request) { "profiles" : profiles, } if len(action) == 0 { - view := gins.View() - buffer, _ := view.ParseContent(` + buffer, _ := gview.ParseContent(` gf ghttp pprof @@ -52,19 +51,19 @@ func (p *utilpprof) Index(r *Request) { } } -func (p *utilpprof) Cmdline(r *Request) { +func (p *utilPprof) Cmdline(r *Request) { netpprof.Cmdline(r.Response.Writer, &r.Request) } -func (p *utilpprof) Profile(r *Request) { +func (p *utilPprof) Profile(r *Request) { netpprof.Profile(r.Response.Writer, &r.Request) } -func (p *utilpprof) Symbol(r *Request) { +func (p *utilPprof) Symbol(r *Request) { netpprof.Symbol(r.Response.Writer, &r.Request) } -func (p *utilpprof) Trace(r *Request) { +func (p *utilPprof) Trace(r *Request) { netpprof.Trace(r.Response.Writer, &r.Request) } @@ -74,7 +73,7 @@ func (s *Server) EnablePprof(pattern...string) { if len(pattern) > 0 { p = pattern[0] } - up := &utilpprof{} + up := &utilPprof{} _, _, uri, _ := s.parsePattern(p) uri = strings.TrimRight(uri, "/") s.BindHandler(uri + "/*action", up.Index) diff --git a/geg/net/ghttp/hot_restart/admin.go b/geg/net/ghttp/hot_restart/admin.go new file mode 100644 index 000000000..55438e229 --- /dev/null +++ b/geg/net/ghttp/hot_restart/admin.go @@ -0,0 +1,12 @@ +package main + +import ( + "gitee.com/johng/gf/g" +) + +func main() { + s := g.Server() + s.EnableAdmin() + s.SetPort(8199) + s.Run() +} \ No newline at end of file From 327c22379e5b85c800765a7fcc9047f26f2fcf79 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 May 2018 22:00:10 +0800 Subject: [PATCH 19/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E6=B5=8B=E8=AF=95=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 26 ++++++++++++++++--- g/net/ghttp/ghttp_server_comm.go | 4 +-- g/net/ghttp/ghttp_server_comm_child.go | 7 +++-- g/net/ghttp/ghttp_server_comm_main.go | 2 +- g/net/ghttp/ghttp_server_graceful.go | 23 +++++++++++++++- g/os/gproc/gproc_comm.go | 14 +++++++--- .../hot_restart/multi_port_and_server.go | 22 +++++++--------- geg/net/ghttp/https/https_http.go | 5 ++-- geg/other/test.go | 3 +++ 9 files changed, 80 insertions(+), 26 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 64cfc481a..8a88a630c 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -225,16 +225,21 @@ func Wait() { // 开启底层Web Server执行 func (s *Server) startServer(fdMap listenerFdMap) { // 开始执行底层Web Server创建,端口监听 - var server *gracefulServer + var server *gracefulServer + var httpsEnabled bool if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { + // ================ // HTTPS + // ================ if len(s.config.HTTPSAddr) == 0 { if len(s.config.Addr) > 0 { s.config.HTTPSAddr = s.config.Addr + s.config.Addr = "" } else { s.config.HTTPSAddr = gDEFAULT_HTTPS_ADDR } } + httpsEnabled = len(s.config.HTTPSAddr) > 0 var array []string var isFd bool if v, ok := fdMap["https"]; ok && len(v) > 0 { @@ -243,8 +248,10 @@ func (s *Server) startServer(fdMap listenerFdMap) { } else { array = strings.Split(s.config.HTTPSAddr, ",") } - for _, v := range array { + if len(v) == 0 { + continue + } go func(addrItem string) { // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 if isFd && runtime.GOOS != "windows" { @@ -264,8 +271,11 @@ func (s *Server) startServer(fdMap listenerFdMap) { }(v) } } + // ================ // HTTP - if len(s.config.Addr) == 0 { + // ================ + // 当HTTPS服务未启用时,默认HTTP地址才会生效 + if !httpsEnabled && len(s.config.Addr) == 0 { s.config.Addr = gDEFAULT_HTTP_ADDR } var array []string @@ -277,6 +287,9 @@ func (s *Server) startServer(fdMap listenerFdMap) { array = strings.Split(s.config.Addr, ",") } for _, v := range array { + if len(v) == 0 { + continue + } go func(addrItem string) { // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 if isFd && runtime.GOOS != "windows" { @@ -316,6 +329,13 @@ func (s *Server) getListenerFdMap() map[string]string { "https" : "", } for _, v := range s.servers { + //switch l := v.listener.(type) { + //case *net.TCPListener: + //default: + // + // tls.NewListener(ln, config) + // + //} if f, e := v.listener.(*net.TCPListener).File(); e == nil { str := v.addr + "#" + gconv.String(f.Fd()) + "," if v.isHttps { diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index a2fa3b527..0203a6892 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -26,9 +26,9 @@ const ( gMSG_HEARTBEAT = 60 gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 - gPROC_HEARTBEAT_TIMEOUT = 5000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 + gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 gPROC_MULTI_CHILD_CLEAR_INTERVAL = 1000 // (毫秒)检测间隔,当存在多个子进程时(往往是重启间隔非常短且频繁造成),需要进行清理,最终留下一个最新的子进程 - gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 5000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 + gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 3000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 ) // 进程信号量监听消息队列 diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index fb0a5dfec..d0744ffd1 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -39,7 +39,7 @@ func onCommChildRestart(pid int, data []byte) { p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) // windows系统无法进行文件描述符操作,只能重启进程 if runtime.GOOS == "windows" { - // windows下使用shutdownWebServers会造成协程阻塞,这里直接使用close强制关闭 + // windows下使用shutdown会造成协程阻塞,这里直接使用close强制关闭 closeWebServers() } else { // 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口 @@ -65,7 +65,7 @@ func onCommChildRestart(pid int, data []byte) { if newPid, err := p.Start(); err == nil { sendProcessMsg(newPid, gMSG_START, buffer) } else { - glog.Errorfln("%d: fork process failed, error:%s", err.Error()) + glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) } } @@ -75,6 +75,7 @@ func onCommChildShutdown(pid int, data []byte) { if runtime.GOOS != "windows" { shutdownWebServers() } + glog.Printfln("%d: shutdown done", gproc.Pid()) } // 更新上一次主进程主动与子进程通信的时间 @@ -93,11 +94,13 @@ func handleChildProcessHeartbeat() { // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return //glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastHeartbeatTime.Val(), gPROC_HEARTBEAT_TIMEOUT) //glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) + glog.Printfln("%d: exit", gproc.Pid()) os.Exit(0) } // 未开启心跳检测的闲置超过一定时间则主动关闭 if !checkHeartbeat.Val() && gproc.Uptime() > gPROC_CHILD_MAX_IDLE_TIME { //glog.Printfln("%d: max idle time exceeded, exit", gproc.Pid()) + glog.Printfln("%d: exit", gproc.Pid()) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index ebabf2e01..c92f9a880 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://gitee.com/johng/gf. // Web Server进程间通信 - 主进程. -// 管理子进程按照规则听话玩,不听话有一百种方法让子进程在本地混不下去. +// 管理子进程按照规则听话玩,不然有一百种方法让子进程在本地混不下去. package ghttp diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index cf7a3cabc..6c1387f83 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -22,7 +22,8 @@ type gracefulServer struct { fd uintptr addr string httpServer *http.Server - listener net.Listener + rawln *net.TCPListener // 原始listener + listener net.Listener // 接口化封装的listener isHttps bool shutdownChan chan bool } @@ -59,10 +60,25 @@ func (s *gracefulServer) ListenAndServe() error { if err != nil { return err } + //file, err := ln.(*net.TCPListener).File() + //if err != nil { + // return err + //} + //s.fd = file.Fd() s.listener = ln return s.doServe() } +// 获得文件描述符 +func (s *gracefulServer) Fd() uintptr { + file, err := s.listener.(*net.TCPListener).File() + //file, err := s.rawln.File() + if err == nil { + return file.Fd() + } + return 0 +} + // 设置自定义fd func (s *gracefulServer) setFd(fd int) { s.fd = uintptr(fd) @@ -88,6 +104,11 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { if err != nil { return err } + //file, err := ln.(*net.TCPListener).File() + //if err != nil { + // return err + //} + //s.fd = file.Fd() s.listener = tls.NewListener(ln, config) s.isHttps = true return s.doServe() diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 518f407d9..2e9a101c5 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -39,8 +39,11 @@ type Msg struct { func init() { path := getCommFilePath(os.Getpid()) if !gfile.Exists(path) { - // 检测存在性 - if err := gfile.Create(path); err != nil { + // 判断是否需要创建通信文件 + commLocker.Lock() + err := gfile.Create(path) + commLocker.UnLock() + if err != nil { glog.Error(err) os.Exit(1) } @@ -50,9 +53,14 @@ func init() { glog.Errorfln("%s is not writable for gproc", path) os.Exit(1) } - // 初始化时读取已有数据(文件修改时间在10秒以内) if gtime.Second() - gfile.MTime(path) < 10 { + // 初始化时读取已有数据(文件修改时间在10秒以内) checkCommBuffer(path) + } else { + // 否则清空旧的数据内容 + commLocker.Lock() + os.Truncate(path, 0) + commLocker.UnLock() } // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 err := gfsnotify.Add(path, func(event *gfsnotify.Event) { diff --git a/geg/net/ghttp/hot_restart/multi_port_and_server.go b/geg/net/ghttp/hot_restart/multi_port_and_server.go index 0dd6e39d9..b0c9cadad 100644 --- a/geg/net/ghttp/hot_restart/multi_port_and_server.go +++ b/geg/net/ghttp/hot_restart/multi_port_and_server.go @@ -3,28 +3,26 @@ package main import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/net/ghttp" + "time" + "gitee.com/johng/gf/g/os/gproc" ) func main() { s1 := g.Server("s1") - s1.BindHandler("/", func(r *ghttp.Request){ - r.Response.Writeln("hello s1") + s1.EnableAdmin() + s1.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("before time, pid:", gproc.Pid()) + time.Sleep(10*time.Second) + r.Response.Write("after time, pid:", gproc.Pid()) }) - s1.BindHandler("/restart", func(r *ghttp.Request){ - r.Response.Writeln("restart server") - r.Server.Restart() - }) - s1.BindHandler("/shutdown", func(r *ghttp.Request){ - r.Response.Writeln("shutdown server") - r.Server.Shutdown() + s1.BindHandler("/pid", func(r *ghttp.Request) { + r.Response.Write(gproc.Pid()) }) s1.SetPort(8199, 8200) s1.Start() s2 := g.Server("s2") - s2.BindHandler("/", func(r *ghttp.Request){ - r.Response.Writeln("hello s2") - }) + s2.EnableAdmin() s2.SetPort(8300, 8080) s2.Start() diff --git a/geg/net/ghttp/https/https_http.go b/geg/net/ghttp/https/https_http.go index 2ab74b08b..4440453d0 100644 --- a/geg/net/ghttp/https/https_http.go +++ b/geg/net/ghttp/https/https_http.go @@ -6,11 +6,12 @@ import ( func main() { s := ghttp.GetServer() + s.EnableAdmin() s.BindHandler("/", func(r *ghttp.Request){ r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!") }) s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") - s.SetHTTPSPort(443) - s.SetPort(80) + s.SetHTTPSPort(8198) + s.SetPort(8199) s.Run() } \ No newline at end of file diff --git a/geg/other/test.go b/geg/other/test.go index 2af9ff714..19b906338 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -6,6 +6,7 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gproc" + "strings" ) // 数据解包,防止黏包 @@ -43,6 +44,8 @@ func checksum(buffer []byte) uint32 { } func main(){ + fmt.Println(len(strings.Split("", ","))) + return b := gfile.GetBinContents("/tmp/gproc/30588") for _, msg := range bufferToMsgs(b) { fmt.Println(msg.Pid) From c739dd0ed693c6703799c3c1a093ed8815c43341 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 14 May 2018 17:35:54 +0800 Subject: [PATCH 20/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E5=9C=A8=E5=90=8C=E4=B8=80=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E5=A4=9AWeb=20Server=E4=B8=8B=E7=9A=84=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 73 ++++++++++--------- g/net/ghttp/ghttp_server_comm_child.go | 14 ++-- g/net/ghttp/ghttp_server_comm_signal_unix.go | 16 ++-- g/net/ghttp/ghttp_server_graceful.go | 13 ++-- .../hot_restart/multi_port_and_server.go | 5 +- geg/net/ghttp/hot_restart/simple.go | 18 ++--- 6 files changed, 73 insertions(+), 66 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 8a88a630c..871ac58ae 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -8,7 +8,6 @@ package ghttp import ( "os" - "net" "sync" "errors" "strings" @@ -241,9 +240,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { } httpsEnabled = len(s.config.HTTPSAddr) > 0 var array []string - var isFd bool if v, ok := fdMap["https"]; ok && len(v) > 0 { - isFd = true array = strings.Split(v, ",") } else { array = strings.Split(s.config.HTTPSAddr, ",") @@ -253,18 +250,25 @@ func (s *Server) startServer(fdMap listenerFdMap) { continue } go func(addrItem string) { - // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 - if isFd && runtime.GOOS != "windows" { - tArray := strings.Split(addrItem, "#") - server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) + fd := 0 + addr := addrItem + array := strings.Split(addrItem, "#") + if len(array) > 1 { + addr = array[0] + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if runtime.GOOS != "windows" { + fd = gconv.Int(array[1]) + } + } + if fd > 0 { + server = s.newGracefulServer(addr, fd) } else { - server = s.newGracefulServer(addrItem) + server = s.newGracefulServer(addr) } s.servers = append(s.servers, server) if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { - s.servers = s.servers[0 : len(s.servers) - 1] glog.Error(err) } } @@ -279,9 +283,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.config.Addr = gDEFAULT_HTTP_ADDR } var array []string - var isFd bool if v, ok := fdMap["http"]; ok && len(v) > 0 { - isFd = true array = strings.Split(v, ",") } else { array = strings.Split(s.config.Addr, ",") @@ -291,18 +293,25 @@ func (s *Server) startServer(fdMap listenerFdMap) { continue } go func(addrItem string) { - // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 - if isFd && runtime.GOOS != "windows" { - tArray := strings.Split(addrItem, "#") - server = s.newGracefulServer(tArray[0], gconv.Int(tArray[1])) + fd := 0 + addr := addrItem + array := strings.Split(addrItem, "#") + if len(array) > 1 { + addr = array[0] + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if runtime.GOOS != "windows" { + fd = gconv.Int(array[1]) + } + } + if fd > 0 { + server = s.newGracefulServer(addr, fd) } else { - server = s.newGracefulServer(addrItem) + server = s.newGracefulServer(addr) } s.servers = append(s.servers, server) if err := server.ListenAndServe(); err != nil { // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { - s.servers = s.servers[0 : len(s.servers) - 1] glog.Error(err) } } @@ -325,34 +334,26 @@ func (s *Server) Shutdown() { // 获取当前监听的文件描述符信息,构造成map返回 func (s *Server) getListenerFdMap() map[string]string { m := map[string]string { - "http" : "", "https" : "", + "http" : "", } + // s.servers是从HTTPS到HTTP优先级遍历,解析的时候也应当按照这个顺序读取fd for _, v := range s.servers { - //switch l := v.listener.(type) { - //case *net.TCPListener: - //default: - // - // tls.NewListener(ln, config) - // - //} - if f, e := v.listener.(*net.TCPListener).File(); e == nil { - str := v.addr + "#" + gconv.String(f.Fd()) + "," - if v.isHttps { - m["https"] += str - } else { - m["http"] += str - } + str := v.addr + "#" + gconv.String(v.Fd()) + "," + if v.isHttps { + m["https"] += str } else { - glog.Errorfln("failed to get listener file: %v", e) + m["http"] += str } } + // 去掉末尾的","号 + if len(m["https"]) > 0 { + m["https"] = m["https"][0 : len(m["https"]) - 1] + } if len(m["http"]) > 0 { m["http"] = m["http"][0 : len(m["http"]) - 1] } - if len(m["https"]) > 0 { - m["https"] = m["https"][0 : len(m["https"]) - 1] - } + return m } diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index d0744ffd1..e1150576c 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -52,8 +52,12 @@ func onCommChildRestart(pid int, data []byte) { for _, item := range strings.Split(fdv, ",") { array := strings.Split(item, "#") fd := uintptr(gconv.Uint(array[1])) - s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles)) - p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, "")) + 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, ",") } @@ -75,7 +79,6 @@ func onCommChildShutdown(pid int, data []byte) { if runtime.GOOS != "windows" { shutdownWebServers() } - glog.Printfln("%d: shutdown done", gproc.Pid()) } // 更新上一次主进程主动与子进程通信的时间 @@ -94,13 +97,12 @@ func handleChildProcessHeartbeat() { // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return //glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastHeartbeatTime.Val(), gPROC_HEARTBEAT_TIMEOUT) //glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) - glog.Printfln("%d: exit", gproc.Pid()) + glog.Printfln("%d: waiting %dms for shutdown timeout, exit", gproc.Pid(), gPROC_HEARTBEAT_TIMEOUT) os.Exit(0) } // 未开启心跳检测的闲置超过一定时间则主动关闭 if !checkHeartbeat.Val() && gproc.Uptime() > gPROC_CHILD_MAX_IDLE_TIME { - //glog.Printfln("%d: max idle time exceeded, exit", gproc.Pid()) - glog.Printfln("%d: exit", gproc.Pid()) + glog.Printfln("%d: max idle time %dms exceeded, exit", gproc.Pid(), gPROC_CHILD_MAX_IDLE_TIME) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_signal_unix.go b/g/net/ghttp/ghttp_server_comm_signal_unix.go index 177ba6067..af56d9356 100644 --- a/g/net/ghttp/ghttp_server_comm_signal_unix.go +++ b/g/net/ghttp/ghttp_server_comm_signal_unix.go @@ -30,16 +30,18 @@ func handleProcessSignal() { for { sig = <- procSignalChan switch sig { - // 进程终止,停止所有子进程运行 - case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - return + // 进程终止,停止所有子进程运行 + case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: + sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + // 强制性kill掉所有子进程 + procManager.KillAll() + return // 用户信号,重启服务 - case syscall.SIGUSR1: - sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) + case syscall.SIGUSR1: + sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) - default: + default: } } } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 6c1387f83..d6a6a9f94 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -71,10 +71,11 @@ func (s *gracefulServer) ListenAndServe() error { // 获得文件描述符 func (s *gracefulServer) Fd() uintptr { - file, err := s.listener.(*net.TCPListener).File() - //file, err := s.rawln.File() - if err == nil { - return file.Fd() + if s.listener != nil { + file, err := s.listener.(*net.TCPListener).File() + if err == nil { + return file.Fd() + } } return 0 } @@ -155,9 +156,9 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { // 执行请求优雅关闭 func (s *gracefulServer) shutdown() { if err := s.httpServer.Shutdown(context.Background()); err != nil { - glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) + //glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) + //glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } diff --git a/geg/net/ghttp/hot_restart/multi_port_and_server.go b/geg/net/ghttp/hot_restart/multi_port_and_server.go index b0c9cadad..536d35cd1 100644 --- a/geg/net/ghttp/hot_restart/multi_port_and_server.go +++ b/geg/net/ghttp/hot_restart/multi_port_and_server.go @@ -11,9 +11,10 @@ func main() { s1 := g.Server("s1") s1.EnableAdmin() s1.BindHandler("/", func(r *ghttp.Request) { - r.Response.Write("before time, pid:", gproc.Pid()) + pid := gproc.Pid() + r.Response.Writeln("before restart, pid:", pid) time.Sleep(10*time.Second) - r.Response.Write("after time, pid:", gproc.Pid()) + r.Response.Writeln("after restart, pid:", gproc.Pid()) }) s1.BindHandler("/pid", func(r *ghttp.Request) { r.Response.Write(gproc.Pid()) diff --git a/geg/net/ghttp/hot_restart/simple.go b/geg/net/ghttp/hot_restart/simple.go index c227c05ec..0129446a8 100644 --- a/geg/net/ghttp/hot_restart/simple.go +++ b/geg/net/ghttp/hot_restart/simple.go @@ -3,21 +3,21 @@ package main import ( "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/os/gproc" + "time" ) func main() { s := g.Server() - s.BindHandler("/", func(r *ghttp.Request){ - r.Response.Writeln("hello") + s.BindHandler("/sleep", func(r *ghttp.Request){ + r.Response.Writeln(gproc.Pid()) + time.Sleep(10*time.Second) + r.Response.Writeln(gproc.Pid()) }) - s.BindHandler("/restart", func(r *ghttp.Request){ - r.Response.Writeln("restart server") - r.Server.Restart() - }) - s.BindHandler("/shutdown", func(r *ghttp.Request){ - r.Response.Writeln("shutdown server") - r.Server.Shutdown() + s.BindHandler("/pid", func(r *ghttp.Request){ + r.Response.Writeln(gproc.Pid()) }) + s.EnableAdmin() s.SetPort(8199) s.Run() } \ No newline at end of file From 964f8b499b7b7fd624a7bc7a48e09b6d7667cd6d Mon Sep 17 00:00:00 2001 From: John Date: Tue, 15 May 2018 15:09:41 +0800 Subject: [PATCH 21/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E6=B5=8B=E8=AF=95=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_comm.go | 8 +++--- g/net/ghttp/ghttp_server_comm_child.go | 1 + g/net/ghttp/ghttp_server_comm_main.go | 40 ++++++++++++-------------- g/net/ghttp/ghttp_server_graceful.go | 9 ++++-- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 0203a6892..67af23d84 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -26,9 +26,9 @@ const ( gMSG_HEARTBEAT = 60 gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 - gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 - gPROC_MULTI_CHILD_CLEAR_INTERVAL = 1000 // (毫秒)检测间隔,当存在多个子进程时(往往是重启间隔非常短且频繁造成),需要进行清理,最终留下一个最新的子进程 - gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 3000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 + gPROC_HEARTBEAT_TIMEOUT = 30000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 + //gPROC_MULTI_CHILD_CLEAR_INTERVAL = 1000 // (毫秒)检测间隔,当存在多个子进程时(往往是重启间隔非常短且频繁造成),需要进行清理,最终留下一个最新的子进程 + //gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 30000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 ) // 进程信号量监听消息队列 @@ -44,7 +44,7 @@ func handleProcessMsgAndSignal() { go handleChildProcessHeartbeat() } else { go handleMainProcessHeartbeat() - go handleMainProcessChildClear() + //go handleMainProcessChildClear() } handleProcessMsg() } diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index e1150576c..f567dc7d2 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -79,6 +79,7 @@ func onCommChildShutdown(pid int, data []byte) { if runtime.GOOS != "windows" { shutdownWebServers() } + os.Exit(0) } // 更新上一次主进程主动与子进程通信的时间 diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index c92f9a880..ec0bb608d 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -11,8 +11,6 @@ package ghttp import ( "os" "time" - "gitee.com/johng/gf/g/os/glog" - "gitee.com/johng/gf/g/os/gproc" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gmap" ) @@ -82,22 +80,22 @@ func handleMainProcessHeartbeat() { } // 清理多余的子进程 -func handleMainProcessChildClear() { - for { - time.Sleep(gPROC_MULTI_CHILD_CLEAR_INTERVAL*time.Millisecond) - if procManager.Size() > 1 { - minPid := 0 - minTime := int(gtime.Millisecond()) - for _, pid := range procManager.Pids() { - if t := procFirstTimeMap.Get(pid); t < minTime { - minPid = pid - minTime = t - } - } - if minPid > 0 && procUpdateTimeMap.Get(minPid) - procFirstTimeMap.Get(minPid) > gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE { - sendProcessMsg(minPid, gMSG_SHUTDOWN, nil) - glog.Printfln("%d: multi child occurred, shutdown %d", gproc.Pid(), minPid) - } - } - } -} \ No newline at end of file +//func handleMainProcessChildClear() { +// for { +// time.Sleep(gPROC_MULTI_CHILD_CLEAR_INTERVAL*time.Millisecond) +// if procManager.Size() > 1 { +// minPid := 0 +// minTime := int(gtime.Millisecond()) +// for _, pid := range procManager.Pids() { +// if t := procFirstTimeMap.Get(pid); t < minTime { +// minPid = pid +// minTime = t +// } +// } +// if minPid > 0 && procUpdateTimeMap.Get(minPid) - procFirstTimeMap.Get(minPid) > gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE { +// sendProcessMsg(minPid, gMSG_SHUTDOWN, nil) +// glog.Printfln("%d: multi child occurred, shutdown %d", gproc.Pid(), minPid) +// } +// } +// } +//} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index d6a6a9f94..d6d2160be 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -15,6 +15,7 @@ import ( "crypto/tls" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gproc" + "time" ) // 优雅的Web Server对象封装 @@ -155,10 +156,12 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { // 执行请求优雅关闭 func (s *gracefulServer) shutdown() { - if err := s.httpServer.Shutdown(context.Background()); err != nil { - //glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second)) + defer cancel() + if err := s.httpServer.Shutdown(ctx); err != nil { + glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - //glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) + glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } From c18e274d06af5a0db3c889ca2759bde5f64352e0 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 15 May 2018 18:34:00 +0800 Subject: [PATCH 22/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E6=B5=8B=E8=AF=95=EF=BC=8C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=94=B9=E8=BF=9B=EF=BC=8CTODO++?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO | 2 +- g/net/ghttp/ghttp_server.go | 88 ++++++++++---------- g/net/ghttp/ghttp_server_admin.go | 16 +++- g/net/ghttp/ghttp_server_comm.go | 55 ++++++------ g/net/ghttp/ghttp_server_comm_child.go | 33 ++++---- g/net/ghttp/ghttp_server_comm_child_unix.go | 8 +- g/net/ghttp/ghttp_server_comm_main.go | 60 ++++++------- g/net/ghttp/ghttp_server_comm_signal_unix.go | 9 +- g/net/ghttp/ghttp_server_graceful.go | 12 +-- g/os/gproc/gproc_comm.go | 33 +++++++- geg/other/test.go | 6 +- 11 files changed, 180 insertions(+), 142 deletions(-) diff --git a/TODO b/TODO index 7b1f2297b..fa2fbbde9 100644 --- a/TODO +++ b/TODO @@ -10,7 +10,7 @@ ON THE WAY: 9. orm增加更多数据库支持; 10. ghttp.Response增加输出内容后自动退出当前请求机制,不需要用户手动return,参考beego如何实现; 11. 当二进制参数为nil时,gjson.LoadContent并将gjson.Json对象ToMap时会报错; - +12. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name,现有的控制器及执行对象注册很难友好支持这种动态形式; DONE: diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 871ac58ae..2541e6eec 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -223,8 +223,6 @@ func Wait() { // 开启底层Web Server执行 func (s *Server) startServer(fdMap listenerFdMap) { - // 开始执行底层Web Server创建,端口监听 - var server *gracefulServer var httpsEnabled bool if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 { // ================ @@ -249,30 +247,21 @@ func (s *Server) startServer(fdMap listenerFdMap) { if len(v) == 0 { continue } - go func(addrItem string) { - fd := 0 - addr := addrItem - array := strings.Split(addrItem, "#") - if len(array) > 1 { - addr = array[0] - // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 - if runtime.GOOS != "windows" { - fd = gconv.Int(array[1]) - } + fd := 0 + addr := v + array := strings.Split(v, "#") + if len(array) > 1 { + addr = array[0] + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if runtime.GOOS != "windows" { + fd = gconv.Int(array[1]) } - if fd > 0 { - server = s.newGracefulServer(addr, fd) - } else { - server = s.newGracefulServer(addr) - } - s.servers = append(s.servers, server) - if err := server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath); err != nil { - // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 - if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { - glog.Error(err) - } - } - }(v) + } + if fd > 0 { + s.servers = append(s.servers, s.newGracefulServer(addr, fd)) + } else { + s.servers = append(s.servers, s.newGracefulServer(addr)) + } } } // ================ @@ -292,28 +281,34 @@ func (s *Server) startServer(fdMap listenerFdMap) { if len(v) == 0 { continue } - go func(addrItem string) { - fd := 0 - addr := addrItem - array := strings.Split(addrItem, "#") - if len(array) > 1 { - addr = array[0] - // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 - if runtime.GOOS != "windows" { - fd = gconv.Int(array[1]) - } + fd := 0 + addr := v + array := strings.Split(v, "#") + if len(array) > 1 { + addr = array[0] + // windows系统不支持文件描述符传递socket通信平滑交接,因此只能完整重启 + if runtime.GOOS != "windows" { + fd = gconv.Int(array[1]) } - if fd > 0 { - server = s.newGracefulServer(addr, fd) + } + if fd > 0 { + s.servers = append(s.servers, s.newGracefulServer(addr, fd)) + } else { + s.servers = append(s.servers, s.newGracefulServer(addr)) + } + } + // 开始执行异步监听 + for _, v := range s.servers { + go func(server *gracefulServer) { + var err error + if server.isHttps { + err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath) } else { - server = s.newGracefulServer(addr) + err = server.ListenAndServe() } - s.servers = append(s.servers, server) - if err := server.ListenAndServe(); err != nil { - // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 - if !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { - glog.Error(err) - } + // 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作 + if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) { + glog.Error(err) } }(v) } @@ -321,6 +316,11 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.status = 1 } +// 热重启Web Server +func (s *Server) Reload() { + sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) +} + // 重启Web Server func (s *Server) Restart() { sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go index 3f5778e68..11c09ea9c 100644 --- a/g/net/ghttp/ghttp_server_admin.go +++ b/g/net/ghttp/ghttp_server_admin.go @@ -10,6 +10,7 @@ package ghttp import ( "strings" "gitee.com/johng/gf/g/os/gview" + "runtime" ) // 用于服务管理的对象 @@ -26,15 +27,26 @@ func (p *utilAdmin) Index(r *Request) { gf ghttp admin +

reload

restart

shutdown

- `, data) + `, data) r.Response.Write(buffer) } -// 服务重启 +// 服务热重启 +func (p *utilAdmin) Reload(r *Request) { + if runtime.GOOS == "windows" { + p.Restart(r) + } else { + r.Response.Write("reload server") + r.Server.Reload() + } +} + +// 服务完整重启 func (p *utilAdmin) Restart(r *Request) { r.Response.Write("restart server") r.Server.Restart() diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 67af23d84..8857fb39e 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -10,32 +10,34 @@ package ghttp import ( "os" "gitee.com/johng/gf/g/os/gproc" - "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" + "gitee.com/johng/gf/g/os/gtime" ) const ( gMSG_START = 10 - gMSG_RESTART = 20 - gMSG_SHUTDOWN = 30 - gMSG_NEW_FORK = 40 - gMSG_REMOVE_PROC = 50 - gMSG_HEARTBEAT = 60 + gMSG_RELOAD = 20 + gMSG_RESTART = 30 + gMSG_SHUTDOWN = 40 + gMSG_CLOSE = 45 + gMSG_NEW_FORK = 50 + gMSG_HEARTBEAT = 70 - gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 - gPROC_HEARTBEAT_TIMEOUT = 30000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 - //gPROC_MULTI_CHILD_CLEAR_INTERVAL = 1000 // (毫秒)检测间隔,当存在多个子进程时(往往是重启间隔非常短且频繁造成),需要进行清理,最终留下一个最新的子进程 - //gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE = 30000 // (毫秒)当多个子进程存在时,允许子进程进程至少运行的最小时间,超过该时间则清理 + gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 + gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 ) // 进程信号量监听消息队列 -var procSignalChan = make(chan os.Signal) +var procSignalChan = make(chan os.Signal) + +// 上一次进程间心跳的时间戳 +var lastUpdateTime = gtype.NewInt() // (主子进程)在第一次创建子进程成功之后才会开始心跳检测,同理对应超时时间才会生效 -var checkHeartbeat = gtype.NewBool() +var checkHeartbeat = gtype.NewBool() // 处理进程信号量监控以及进程间消息通信 func handleProcessMsgAndSignal() { @@ -44,7 +46,6 @@ func handleProcessMsgAndSignal() { go handleChildProcessHeartbeat() } else { go handleMainProcessHeartbeat() - //go handleMainProcessChildClear() } handleProcessMsg() } @@ -60,21 +61,20 @@ func handleProcessMsg() { //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] + if msg.Pid != gproc.Pid() { + updateProcessUpdateTime() + } if gproc.IsChild() { // =============== // 子进程 // =============== - // 任何与父进程的通信都会更新最后通信时间 - if msg.Pid == gproc.PPid() { - updateProcessChildUpdateTime() - } switch act { case gMSG_START: onCommChildStart(msg.Pid, data) + case gMSG_RELOAD: onCommChildReload(msg.Pid, data) case gMSG_RESTART: onCommChildRestart(msg.Pid, data) + case gMSG_CLOSE: onCommChildClose(msg.Pid, data) case gMSG_HEARTBEAT: onCommChildHeartbeat(msg.Pid, data) - case gMSG_SHUTDOWN: - onCommChildShutdown(msg.Pid, data) - return + case gMSG_SHUTDOWN: onCommChildShutdown(msg.Pid, data) } } else { // =============== @@ -84,20 +84,12 @@ func handleProcessMsg() { if msg.Pid != gproc.Pid() { updateProcessCommTime(msg.Pid) } - if !procFirstTimeMap.Contains(msg.Pid) { - procFirstTimeMap.Set(msg.Pid, int(gtime.Millisecond())) - } switch act { case gMSG_START: onCommMainStart(msg.Pid, data) + case gMSG_RELOAD: onCommMainReload(msg.Pid, data) case gMSG_RESTART: onCommMainRestart(msg.Pid, data) case gMSG_NEW_FORK: onCommMainNewFork(msg.Pid, data) case gMSG_HEARTBEAT: onCommMainHeartbeat(msg.Pid, data) - case gMSG_REMOVE_PROC: - onCommMainRemoveProc(msg.Pid, data) - // 如果所有子进程都退出,那么主进程也主动退出 - if procManager.Size() == 0 { - return - } case gMSG_SHUTDOWN: onCommMainShutdown(msg.Pid, data) return @@ -166,4 +158,9 @@ func closeWebServers() { } } }) +} + +// 更新上一次进程间通信的时间 +func updateProcessUpdateTime() { + lastUpdateTime.Set(int(gtime.Millisecond())) } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index f567dc7d2..2839ca9e2 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -18,23 +18,19 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/encoding/gjson" - "gitee.com/johng/gf/g/container/gtype" ) const ( gPROC_CHILD_MAX_IDLE_TIME = 3000 // 子进程闲置时间(未开启心跳机制的时间) ) -// (子进程)上一次从主进程接收心跳的时间戳 -var lastHeartbeatTime = gtype.NewInt() - // 心跳消息 func onCommChildHeartbeat(pid int, data []byte) { - updateProcessChildUpdateTime() + } -// 子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 -func onCommChildRestart(pid int, data []byte) { +// 热重启,子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 +func onCommChildReload(pid int, data []byte) { var buffer []byte = nil p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) // windows系统无法进行文件描述符操作,只能重启进程 @@ -73,18 +69,23 @@ func onCommChildRestart(pid int, data []byte) { } } -// 关闭服务链接并退出 +// 完整重启 +func onCommChildRestart(pid int, data []byte) { + sendProcessMsg(gproc.PPid(), gMSG_RESTART, nil) +} + +// 优雅关闭服务链接并退出 func onCommChildShutdown(pid int, data []byte) { - sendProcessMsg(gproc.PPid(), gMSG_REMOVE_PROC, nil) if runtime.GOOS != "windows" { shutdownWebServers() } os.Exit(0) } -// 更新上一次主进程主动与子进程通信的时间 -func updateProcessChildUpdateTime() { - lastHeartbeatTime.Set(int(gtime.Millisecond())) +// 强制性关闭服务链接并退出 +func onCommChildClose(pid int, data []byte) { + closeWebServers() + os.Exit(0) } // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 @@ -93,17 +94,15 @@ func handleChildProcessHeartbeat() { time.Sleep(gPROC_HEARTBEAT_INTERVAL*time.Millisecond) sendProcessMsg(gproc.PPid(), gMSG_HEARTBEAT, nil) // 超过时间没有接收到主进程心跳,自动关闭退出 - if checkHeartbeat.Val() && (int(gtime.Millisecond()) - lastHeartbeatTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + if checkHeartbeat.Val() && (int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return //glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastHeartbeatTime.Val(), gPROC_HEARTBEAT_TIMEOUT) - //glog.Printfln("%d: heartbeat timeout, exit", gproc.Pid()) - glog.Printfln("%d: waiting %dms for shutdown timeout, exit", gproc.Pid(), gPROC_HEARTBEAT_TIMEOUT) + glog.Printfln("%d: heartbeat timeout[%dms], exit", gproc.Pid(), gPROC_HEARTBEAT_TIMEOUT) os.Exit(0) } // 未开启心跳检测的闲置超过一定时间则主动关闭 if !checkHeartbeat.Val() && gproc.Uptime() > gPROC_CHILD_MAX_IDLE_TIME { - glog.Printfln("%d: max idle time %dms exceeded, exit", gproc.Pid(), gPROC_CHILD_MAX_IDLE_TIME) + glog.Printfln("%d: idle timeout[%dms], exit", gproc.Pid(), gPROC_CHILD_MAX_IDLE_TIME) os.Exit(0) } } diff --git a/g/net/ghttp/ghttp_server_comm_child_unix.go b/g/net/ghttp/ghttp_server_comm_child_unix.go index f112dd55f..20485b93a 100644 --- a/g/net/ghttp/ghttp_server_comm_child_unix.go +++ b/g/net/ghttp/ghttp_server_comm_child_unix.go @@ -10,6 +10,7 @@ package ghttp import ( + "os" "gitee.com/johng/gf/g/os/gproc" ) @@ -31,9 +32,12 @@ func onCommChildStart(pid int, data []byte) { sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 if gproc.PPidOS() != gproc.PPid() { - sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) + //sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) + if p, err := os.FindProcess(gproc.PPidOS()); err == nil { + p.Kill() + } } // 开始心跳时必须保证主进程时间有值,但是又不能等待主进程消息后再开始检测,因此这里自己更新一下通信时间 - updateProcessChildUpdateTime() + updateProcessUpdateTime() checkHeartbeat.Set(true) } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index ec0bb608d..37a4ecc72 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -13,12 +13,9 @@ import ( "time" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/os/gproc" ) -// (主进程)子进程与主进程第一次通信的时间映射map -// 用以识别子进程创建时间先后顺序,当存在多个子进程时,主动销毁旧的子进程 -var procFirstTimeMap = gmap.NewIntIntMap() - // (主进程)主进程与子进程上一次活跃时间映射map var procUpdateTimeMap = gmap.NewIntIntMap() @@ -34,10 +31,23 @@ func onCommMainHeartbeat(pid int, data []byte) { updateProcessCommTime(pid) } -// 重启服务 -func onCommMainRestart(pid int, data []byte) { +// 热重启服务 +func onCommMainReload(pid int, data []byte) { // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 - procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) + procManager.Send(formatMsgBuffer(gMSG_RELOAD, nil)) +} + +// 完整重启服务 +func onCommMainRestart(pid int, data []byte) { + if pid == gproc.Pid() { + procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) + return + } + if p, _ := os.FindProcess(pid); p != nil { + p.Kill() + p.Wait() + } + sendProcessMsg(gproc.Pid(), gMSG_START, nil) } // 新建子进程通知 @@ -46,14 +56,11 @@ func onCommMainNewFork(pid int, data []byte) { checkHeartbeat.Set(true) } -// 销毁子进程通知 -func onCommMainRemoveProc(pid int, data []byte) { - procManager.RemoveProcess(pid) -} - // 关闭服务,通知所有子进程退出 func onCommMainShutdown(pid int, data []byte) { - procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) + //procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) + procManager.KillAll() + procManager.WaitAll() } // 更新指定进程的通信时间记录 @@ -72,30 +79,13 @@ func handleMainProcessHeartbeat() { if int(gtime.Millisecond()) - procUpdateTimeMap.Get(pid) > gPROC_HEARTBEAT_TIMEOUT { // 这里需要手动从进程管理器中去掉该进程 procManager.RemoveProcess(pid) - sendProcessMsg(pid, gMSG_SHUTDOWN, nil) + sendProcessMsg(pid, gMSG_CLOSE, nil) } } } + // 如果所有子进程都退出,并且达到超时时间,那么主进程也没存在的必要 + if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ + os.Exit(0) + } } } - -// 清理多余的子进程 -//func handleMainProcessChildClear() { -// for { -// time.Sleep(gPROC_MULTI_CHILD_CLEAR_INTERVAL*time.Millisecond) -// if procManager.Size() > 1 { -// minPid := 0 -// minTime := int(gtime.Millisecond()) -// for _, pid := range procManager.Pids() { -// if t := procFirstTimeMap.Get(pid); t < minTime { -// minPid = pid -// minTime = t -// } -// } -// if minPid > 0 && procUpdateTimeMap.Get(minPid) - procFirstTimeMap.Get(minPid) > gPROC_MULTI_CHILD_CLEAR_MIN_EXPIRE { -// sendProcessMsg(minPid, gMSG_SHUTDOWN, nil) -// glog.Printfln("%d: multi child occurred, shutdown %d", gproc.Pid(), minPid) -// } -// } -// } -//} \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_signal_unix.go b/g/net/ghttp/ghttp_server_comm_signal_unix.go index af56d9356..760212c07 100644 --- a/g/net/ghttp/ghttp_server_comm_signal_unix.go +++ b/g/net/ghttp/ghttp_server_comm_signal_unix.go @@ -26,6 +26,7 @@ func handleProcessSignal() { syscall.SIGHUP, syscall.SIGTERM, syscall.SIGUSR1, + syscall.SIGUSR2, ) for { sig = <- procSignalChan @@ -33,12 +34,14 @@ func handleProcessSignal() { // 进程终止,停止所有子进程运行 case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) - // 强制性kill掉所有子进程 - procManager.KillAll() return - // 用户信号,重启服务 + // 用户信号,热重启服务 case syscall.SIGUSR1: + sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) + + // 用户信号,完整重启服务 + case syscall.SIGUSR2: sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) default: diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index d6d2160be..8d93af987 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -18,6 +18,10 @@ import ( "time" ) +const ( + gGRACEFUL_SHUTDOWN_TIMEOUT = 10*time.Second // 优雅关闭链接时的超时时间 +) + // 优雅的Web Server对象封装 type gracefulServer struct { fd uintptr @@ -156,12 +160,10 @@ func (s *gracefulServer) getNetListener(addr string) (net.Listener, error) { // 执行请求优雅关闭 func (s *gracefulServer) shutdown() { - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second)) - defer cancel() - if err := s.httpServer.Shutdown(ctx); err != nil { + if err := s.httpServer.Shutdown(context.Background()); err != nil { glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) + //glog.Printfln("%d: %s server [%s] shutdown smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } @@ -171,7 +173,7 @@ func (s *gracefulServer) close() { if err := s.httpServer.Close(); err != nil { glog.Errorfln("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) } else { - glog.Printfln("%d: %s server [%s] closed smoothly", gproc.Pid(), s.getProto(), s.addr) + //glog.Printfln("%d: %s server [%s] closed smoothly", gproc.Pid(), s.getProto(), s.addr) s.shutdownChan <- true } } diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 2e9a101c5..9ef920e3d 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -9,6 +9,7 @@ package gproc import ( "os" "fmt" + "time" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gflock" @@ -21,7 +22,9 @@ import ( const ( // 由于子进程的temp dir有可能会和父进程不一致(特别是windows下),影响进程间通信,这里统一使用环境变量设置 - gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" + gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" + // 自动通信文件清理时间间隔 + gPROC_COMM_AUTO_CLEAR_INTERVAL = time.Second ) // 当前进程的文件锁 @@ -69,6 +72,23 @@ func init() { if err != nil { glog.Error(err) } + + go autoClearCommDir() +} + +// 自动清理通信目录文件 +// @todo 目前是以时间过期规则进行清理,后期可以考虑加入进程存在性判断 +func autoClearCommDir() { + dirPath := getCommDirPath() + for { + time.Sleep(gPROC_COMM_AUTO_CLEAR_INTERVAL) + for _, name := range gfile.ScanDir(dirPath) { + path := dirPath + gfile.Separator + name + if gtime.Second() - gfile.MTime(path) >= 10 { + gfile.Remove(path) + } + } + } } // 手动检查进程通信消息,如果存在消息曾推送到进程消息队列 @@ -97,6 +117,10 @@ func Receive() *Msg { // 向指定gproc进程发送数据 // 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) func Send(pid int, data interface{}) error { + // 首先检测进程存在不存在,存在才能发送消息 + if _, err := os.FindProcess(pid); err != nil { + return err + } buffer := gconv.Bytes(data) b := make([]byte, 0) b = append(b, gbinary.EncodeInt32(int32(len(buffer) + 12))...) @@ -112,11 +136,16 @@ func Send(pid int, data interface{}) error { // 获取指定进程的通信文件地址 func getCommFilePath(pid int) string { + return getCommDirPath() + gfile.Separator + gconv.String(pid) +} + +// 获取进程间通信目录地址 +func getCommDirPath() string { tempDir := os.Getenv("gproc.tempdir") if tempDir == "" { tempDir = gfile.TempDir() } - return tempDir + gfile.Separator + "gproc" + gfile.Separator + gconv.String(pid) + return tempDir + gfile.Separator + "gproc" } // 数据解包,防止黏包 diff --git a/geg/other/test.go b/geg/other/test.go index 19b906338..57b793a10 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -6,7 +6,8 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gproc" - "strings" + "os" + "syscall" ) // 数据解包,防止黏包 @@ -44,7 +45,8 @@ func checksum(buffer []byte) uint32 { } func main(){ - fmt.Println(len(strings.Split("", ","))) + p, _ := os.FindProcess(10354) + fmt.Println(p.Signal(syscall.Signal(1))) return b := gfile.GetBinContents("/tmp/gproc/30588") for _, msg := range bufferToMsgs(b) { From d4b64a5bcf8c23689f4c36ae3f696d36e13064a2 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 15 May 2018 23:28:44 +0800 Subject: [PATCH 23/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 6 ++-- g/net/ghttp/ghttp_server_comm.go | 14 ++++---- g/net/ghttp/ghttp_server_comm_child.go | 2 +- g/net/ghttp/ghttp_server_comm_child_unix.go | 2 +- g/net/ghttp/ghttp_server_comm_main.go | 12 +++---- g/os/gflock/gflock.go | 40 +++++++++++++++++++-- g/os/gproc/gproc_comm.go | 13 ++++--- geg/os/gflock/gflock.go | 4 +-- 8 files changed, 67 insertions(+), 26 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 2541e6eec..13ade8479 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -316,19 +316,19 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.status = 1 } -// 热重启Web Server +// 平滑重启Web Server func (s *Server) Reload() { sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) } -// 重启Web Server +// 完整重启Web Server func (s *Server) Restart() { sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) } // 关闭Web Server func (s *Server) Shutdown() { - sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + sendProcessMsg(gproc.PPid(), gMSG_SHUTDOWN, nil) } // 获取当前监听的文件描述符信息,构造成map返回 diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 8857fb39e..57b78439d 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -18,13 +18,13 @@ import ( ) const ( - gMSG_START = 10 - gMSG_RELOAD = 20 - gMSG_RESTART = 30 - gMSG_SHUTDOWN = 40 - gMSG_CLOSE = 45 - gMSG_NEW_FORK = 50 - gMSG_HEARTBEAT = 70 + gMSG_START = 1 + gMSG_RELOAD = 2 + gMSG_RESTART = 3 + gMSG_SHUTDOWN = 4 + gMSG_CLOSE = 5 + gMSG_NEW_FORK = 6 + gMSG_HEARTBEAT = 7 gPROC_HEARTBEAT_INTERVAL = 1000 // (毫秒)进程间心跳间隔 gPROC_HEARTBEAT_TIMEOUT = 3000 // (毫秒)进程间心跳超时时间,如果子进程在这段内没有接收到任何心跳,那么自动退出,防止可能出现的僵尸子进程 diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index 2839ca9e2..5938c1c1d 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -29,7 +29,7 @@ func onCommChildHeartbeat(pid int, data []byte) { } -// 热重启,子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 +// 平滑重启,子进程收到重启消息,那么将自身的ServerFdMap信息收集后发送给主进程,由主进程进行统一调度 func onCommChildReload(pid int, data []byte) { var buffer []byte = nil p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) diff --git a/g/net/ghttp/ghttp_server_comm_child_unix.go b/g/net/ghttp/ghttp_server_comm_child_unix.go index 20485b93a..7d8d84988 100644 --- a/g/net/ghttp/ghttp_server_comm_child_unix.go +++ b/g/net/ghttp/ghttp_server_comm_child_unix.go @@ -32,7 +32,7 @@ func onCommChildStart(pid int, data []byte) { sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 if gproc.PPidOS() != gproc.PPid() { - //sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) + //如果子进程已经继承了父进程的socket文件描述符,那么父进程没有存在的必要,直接kill掉 if p, err := os.FindProcess(gproc.PPidOS()); err == nil { p.Kill() } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 37a4ecc72..5666fbfbf 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -31,19 +31,20 @@ func onCommMainHeartbeat(pid int, data []byte) { updateProcessCommTime(pid) } -// 热重启服务 +// 平滑重启服务 func onCommMainReload(pid int, data []byte) { - // 向所有子进程发送重启命令,子进程将会搜集Web Server信息发送给父进程进行协调重启工作 procManager.Send(formatMsgBuffer(gMSG_RELOAD, nil)) } // 完整重启服务 func onCommMainRestart(pid int, data []byte) { + // 如果是父进程接收到重启指令,那么通知所有子进程重启 if pid == gproc.Pid() { procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) return } - if p, _ := os.FindProcess(pid); p != nil { + // 否则杀掉子进程,然后新建一个完整的子进程 + if p, err := os.FindProcess(pid); err == nil && p != nil { p.Kill() p.Wait() } @@ -56,9 +57,8 @@ func onCommMainNewFork(pid int, data []byte) { checkHeartbeat.Set(true) } -// 关闭服务,通知所有子进程退出 +// 关闭服务,通知所有子进程退出(Kill强制性退出) func onCommMainShutdown(pid int, data []byte) { - //procManager.Send(formatMsgBuffer(gMSG_SHUTDOWN, nil)) procManager.KillAll() procManager.WaitAll() } @@ -83,7 +83,7 @@ func handleMainProcessHeartbeat() { } } } - // 如果所有子进程都退出,并且达到超时时间,那么主进程也没存在的必要 + // 如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ os.Exit(0) } diff --git a/g/os/gflock/gflock.go b/g/os/gflock/gflock.go index c8d10a849..92630fac0 100644 --- a/g/os/gflock/gflock.go +++ b/g/os/gflock/gflock.go @@ -15,8 +15,9 @@ import ( // 文件锁 type Locker struct { - mu sync.RWMutex - flock *flock.Flock + mu sync.RWMutex // 用于外部接口调用的互斥锁(阻塞机制) + fmu sync.RWMutex // 用于保证方法内部操作的原子性互斥锁 + flock *flock.Flock // 底层文件锁对象 } // 创建文件锁 @@ -36,22 +37,57 @@ func (l *Locker) Path() string { return l.flock.Path() } +// 当前文件锁是否处于锁定状态(Lock) +func (l *Locker) IsLocked() bool { + return l.flock.Locked() +} + +// 尝试Lock文件,如果失败立即返回 +func (l *Locker) TryLock() bool { + l.fmu.Lock() + defer l.fmu.Unlock() + ok, _ := l.flock.TryLock() + if ok { + l.mu.Lock() + } + return ok +} + +// 尝试RLock文件,如果失败立即返回 +func (l *Locker) TryRLock() bool { + l.fmu.Lock() + defer l.fmu.Unlock() + ok, _ := l.flock.TryRLock() + if ok { + l.mu.RLock() + } + return ok +} + func (l *Locker) Lock() { + l.fmu.Lock() + defer l.fmu.Unlock() l.mu.Lock() l.flock.Lock() } func (l *Locker) UnLock() { + l.fmu.Lock() + defer l.fmu.Unlock() l.flock.Unlock() l.mu.Unlock() } func (l *Locker) RLock() { + l.fmu.Lock() + defer l.fmu.Unlock() l.mu.RLock() l.flock.RLock() } func (l *Locker) RUnlock() { + l.fmu.Lock() + defer l.fmu.Unlock() l.flock.Unlock() l.mu.RUnlock() } diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 9ef920e3d..027895574 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -27,6 +27,8 @@ const ( gPROC_COMM_AUTO_CLEAR_INTERVAL = time.Second ) +// 全局通信文件清理文件锁(同一时刻只能存在一个进程进行通信文件清理) +var commClearLocker = gflock.New("comm.clear.lock") // 当前进程的文件锁 var commLocker = gflock.New(fmt.Sprintf("%d.lock", os.Getpid())) // 进程通信消息队列 @@ -82,11 +84,14 @@ func autoClearCommDir() { dirPath := getCommDirPath() for { time.Sleep(gPROC_COMM_AUTO_CLEAR_INTERVAL) - for _, name := range gfile.ScanDir(dirPath) { - path := dirPath + gfile.Separator + name - if gtime.Second() - gfile.MTime(path) >= 10 { - gfile.Remove(path) + if commClearLocker.TryLock() { + for _, name := range gfile.ScanDir(dirPath) { + path := dirPath + gfile.Separator + name + if gtime.Second() - gfile.MTime(path) >= 10 { + gfile.Remove(path) + } } + commClearLocker.UnLock() } } } diff --git a/geg/os/gflock/gflock.go b/geg/os/gflock/gflock.go index b2ad294d7..2ceac44f0 100644 --- a/geg/os/gflock/gflock.go +++ b/geg/os/gflock/gflock.go @@ -9,9 +9,9 @@ import ( func main() { l := gflock.New("1.lock") fmt.Println(l.Path()) - fmt.Println(l.Lock()) + fmt.Println(l.TryLock()) fmt.Println("lock 1") - fmt.Println(l.Lock()) + l.Lock() fmt.Println("lock 1") time.Sleep(time.Hour) } From 6ca634e85b662ff8e8b0d9f72d9f1e19f02bddc6 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 May 2018 17:19:16 +0800 Subject: [PATCH 24/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=9C=A8windows=E7=B3=BB=E7=BB=9F=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E5=85=BC=E5=AE=B9=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_comm_child_windows.go | 12 +++++------- g/os/gflock/gflock.go | 13 ------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/g/net/ghttp/ghttp_server_comm_child_windows.go b/g/net/ghttp/ghttp_server_comm_child_windows.go index 54a0455a4..fc1bf9463 100644 --- a/g/net/ghttp/ghttp_server_comm_child_windows.go +++ b/g/net/ghttp/ghttp_server_comm_child_windows.go @@ -7,8 +7,8 @@ package ghttp import ( - "os" "gitee.com/johng/gf/g/os/gproc" + "os" ) // 开启所有Web Server(根据消息启动) @@ -17,11 +17,9 @@ func onCommChildStart(pid int, data []byte) { sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 if gproc.PPidOS() != gproc.PPid() { - sendProcessMsg(gproc.PPidOS(), gMSG_SHUTDOWN, nil) - // 在windows下必须等待父进程销毁后才能表明Server资源已被释放,才能开始端口监听,否则会端口资源冲突 - if p, err := os.FindProcess(gproc.PPidOS()); err == nil { - p.Wait() - } + if p, err := os.FindProcess(gproc.PPidOS()); err == nil { + p.Kill() + } } // 开启Web Server服务 serverMapping.RLockFunc(func(m map[string]interface{}) { @@ -30,6 +28,6 @@ func onCommChildStart(pid int, data []byte) { } }) // 开始心跳时必须保证主进程时间有值,但是又不能等待主进程消息后再开始检测,因此这里自己更新一下通信时间 - updateProcessChildUpdateTime() + updateProcessUpdateTime() checkHeartbeat.Set(true) } \ No newline at end of file diff --git a/g/os/gflock/gflock.go b/g/os/gflock/gflock.go index 92630fac0..70835a3b4 100644 --- a/g/os/gflock/gflock.go +++ b/g/os/gflock/gflock.go @@ -16,7 +16,6 @@ import ( // 文件锁 type Locker struct { mu sync.RWMutex // 用于外部接口调用的互斥锁(阻塞机制) - fmu sync.RWMutex // 用于保证方法内部操作的原子性互斥锁 flock *flock.Flock // 底层文件锁对象 } @@ -44,8 +43,6 @@ func (l *Locker) IsLocked() bool { // 尝试Lock文件,如果失败立即返回 func (l *Locker) TryLock() bool { - l.fmu.Lock() - defer l.fmu.Unlock() ok, _ := l.flock.TryLock() if ok { l.mu.Lock() @@ -55,8 +52,6 @@ func (l *Locker) TryLock() bool { // 尝试RLock文件,如果失败立即返回 func (l *Locker) TryRLock() bool { - l.fmu.Lock() - defer l.fmu.Unlock() ok, _ := l.flock.TryRLock() if ok { l.mu.RLock() @@ -65,29 +60,21 @@ func (l *Locker) TryRLock() bool { } func (l *Locker) Lock() { - l.fmu.Lock() - defer l.fmu.Unlock() l.mu.Lock() l.flock.Lock() } func (l *Locker) UnLock() { - l.fmu.Lock() - defer l.fmu.Unlock() l.flock.Unlock() l.mu.Unlock() } func (l *Locker) RLock() { - l.fmu.Lock() - defer l.fmu.Unlock() l.mu.RLock() l.flock.RLock() } func (l *Locker) RUnlock() { - l.fmu.Lock() - defer l.fmu.Unlock() l.flock.Unlock() l.mu.RUnlock() } From f71579f9f4cd51ff2f9bc3cad89dd93f5549bc1d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 May 2018 17:37:40 +0800 Subject: [PATCH 25/31] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E5=8F=8A?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=AF=B9=E8=B1=A1=E6=B3=A8=E5=86=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9B=B4=E7=81=B5=E6=B4=BB=E7=9A=84=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/http_server_service.go | 34 ++++++++++++++++----------- geg/frame/mvc/controller/demo/rule.go | 19 +++++++++++++++ geg/frame/mvc/main.go | 6 ++--- 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 geg/frame/mvc/controller/demo/rule.go diff --git a/g/net/ghttp/http_server_service.go b/g/net/ghttp/http_server_service.go index 54a907b36..678d3e652 100644 --- a/g/net/ghttp/http_server_service.go +++ b/g/net/ghttp/http_server_service.go @@ -34,23 +34,29 @@ func (s *Server)bindHandlerByMap(m HandlerMap) error { return nil } -// 将方法名称按照设定的规则转换为URI并附加到指定的URI后面 -func (s *Server)appendMethodNameToUriWithPattern(pattern string, name string) string { +// 将方法名称按照设定的规则合并到pattern中. +// 规则1:pattern中的URI包含{method}关键字,则替换该关键字为方法名称 +// 规则2:如果不满足规则1,那么直接将防发明附加到pattern中的URI后面 +func (s *Server)mergeMethodNameToPattern(pattern string, name string) string { + // 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词 + method := "" + for i := 0; i < len(name); i++ { + if i > 0 && gutil.IsLetterUpper(name[i]) { + method += "-" + } + method += strings.ToLower(string(name[i])) + } + if strings.Index(pattern, "{method}") != -1 { + return strings.Replace(pattern, "{method}", method, -1) + } // 检测域名后缀 array := strings.Split(pattern, "@") // 分离URI(其实可能包含HTTP Method) uri := array[0] uri = strings.TrimRight(uri, "/") + "/" - // 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词 - for i := 0; i < len(name); i++ { - if i > 0 && gutil.IsLetterUpper(name[i]) { - uri += "-" - } - uri += strings.ToLower(string(name[i])) - } // 加上指定域名后缀 if len(array) > 1 { - uri += "@" + array[1] + return uri + "@" + array[1] } return uri } @@ -72,7 +78,7 @@ func (s *Server)BindObject(pattern string, obj interface{}) error { t := v.Type() for i := 0; i < v.NumMethod(); i++ { name := t.Method(i).Name - key := s.appendMethodNameToUriWithPattern(pattern, name) + key := s.mergeMethodNameToPattern(pattern, name) m[key] = &HandlerItem { ctype : nil, fname : "", @@ -100,7 +106,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string if !fval.IsValid() { return errors.New("invalid method name:" + name) } - key := s.appendMethodNameToUriWithPattern(pattern, name) + key := s.mergeMethodNameToPattern(pattern, name) m[key] = &HandlerItem{ ctype : nil, fname : "", @@ -152,7 +158,7 @@ func (s *Server)BindController(pattern string, c Controller) error { if name == "Init" || name == "Shut" || name == "Exit" { continue } - key := s.appendMethodNameToUriWithPattern(pattern, name) + key := s.mergeMethodNameToPattern(pattern, name) m[key] = &HandlerItem { ctype : v.Elem().Type(), fname : name, @@ -181,7 +187,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, methods strin if !cval.MethodByName(name).IsValid() { return errors.New("invalid method name:" + name) } - key := s.appendMethodNameToUriWithPattern(pattern, name) + key := s.mergeMethodNameToPattern(pattern, name) m[key] = &HandlerItem { ctype : ctype, fname : name, diff --git a/geg/frame/mvc/controller/demo/rule.go b/geg/frame/mvc/controller/demo/rule.go new file mode 100644 index 000000000..224f9a885 --- /dev/null +++ b/geg/frame/mvc/controller/demo/rule.go @@ -0,0 +1,19 @@ +package demo + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/frame/gmvc" +) + +type ControllerRule struct { + gmvc.Controller +} + +func init() { + g.Server().BindController("/rule/{method}/:name", &ControllerRule{}) +} + +func (c *ControllerRule) Show() { + c.Response.Write(c.Request.Get("name")) +} + diff --git a/geg/frame/mvc/main.go b/geg/frame/mvc/main.go index e4161b36b..0544de505 100644 --- a/geg/frame/mvc/main.go +++ b/geg/frame/mvc/main.go @@ -1,11 +1,11 @@ package main import ( - "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g" _ "gitee.com/johng/gf/geg/frame/mvc/controller/demo" ) func main() { - ghttp.GetServer().SetPort(8199) - ghttp.GetServer().Run() + g.Server().SetPort(8199) + g.Server().Run() } From 5bbf4117412b1d7fca48e5901d4d782641c8a517 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 May 2018 21:27:27 +0800 Subject: [PATCH 26/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E7=89=B9=E6=80=A7=E7=9A=84HTTP&HTTPS?= =?UTF-8?q?=E5=9C=A8Linux=E4=B8=8B=E7=9A=84=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 7 +++- g/net/ghttp/ghttp_server_comm_main.go | 3 +- g/net/ghttp/ghttp_server_comm_signal_unix.go | 3 ++ g/net/ghttp/ghttp_server_graceful.go | 40 ++++++++----------- g/os/gproc/gproc.go | 7 +++- geg/net/ghttp/https/https_http.go | 5 ++- .../ghttp/{hot_restart => reload}/admin.go | 0 geg/net/ghttp/reload/https_http.go | 18 +++++++++ .../{hot_restart => reload}/multi_port.go | 0 .../multi_port_and_server.go | 0 .../ghttp/{hot_restart => reload}/simple.go | 0 11 files changed, 53 insertions(+), 30 deletions(-) rename geg/net/ghttp/{hot_restart => reload}/admin.go (100%) create mode 100644 geg/net/ghttp/reload/https_http.go rename geg/net/ghttp/{hot_restart => reload}/multi_port.go (100%) rename geg/net/ghttp/{hot_restart => reload}/multi_port_and_server.go (100%) rename geg/net/ghttp/{hot_restart => reload}/simple.go (100%) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 13ade8479..773155342 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -127,7 +127,11 @@ func init() { doneChan <- struct{}{} if !gproc.IsChild() { - glog.Printfln("%d: all web server shutdown smoothly", gproc.Pid()) + if serverMapping.Size() > 1 { + glog.Printfln("%d: all web servers shutdown", gproc.Pid()) + } else { + glog.Printfln("%d: web server shutdown", gproc.Pid()) + } } }() } @@ -262,6 +266,7 @@ func (s *Server) startServer(fdMap listenerFdMap) { } else { s.servers = append(s.servers, s.newGracefulServer(addr)) } + s.servers[len(s.servers) - 1].isHttps = true } } // ================ diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 5666fbfbf..04fa6a8ed 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -59,6 +59,7 @@ func onCommMainNewFork(pid int, data []byte) { // 关闭服务,通知所有子进程退出(Kill强制性退出) func onCommMainShutdown(pid int, data []byte) { + procManager.Send(formatMsgBuffer(gMSG_CLOSE, nil)) procManager.KillAll() procManager.WaitAll() } @@ -83,7 +84,7 @@ func handleMainProcessHeartbeat() { } } } - // 如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 + // (双保险)如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ os.Exit(0) } diff --git a/g/net/ghttp/ghttp_server_comm_signal_unix.go b/g/net/ghttp/ghttp_server_comm_signal_unix.go index 760212c07..0c8316419 100644 --- a/g/net/ghttp/ghttp_server_comm_signal_unix.go +++ b/g/net/ghttp/ghttp_server_comm_signal_unix.go @@ -34,6 +34,9 @@ func handleProcessSignal() { // 进程终止,停止所有子进程运行 case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM: sendProcessMsg(gproc.Pid(), gMSG_SHUTDOWN, nil) + if gproc.IsChild() { + sendProcessMsg(gproc.PPid(), gMSG_SHUTDOWN, nil) + } return // 用户信号,热重启服务 diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 8d93af987..13a17bdf2 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -7,19 +7,14 @@ package ghttp import ( + "os" "fmt" "net" - "os" "context" "net/http" "crypto/tls" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gproc" - "time" -) - -const ( - gGRACEFUL_SHUTDOWN_TIMEOUT = 10*time.Second // 优雅关闭链接时的超时时间 ) // 优雅的Web Server对象封装 @@ -27,8 +22,8 @@ type gracefulServer struct { fd uintptr addr string httpServer *http.Server - rawln *net.TCPListener // 原始listener - listener net.Listener // 接口化封装的listener + rawListener net.Listener // 原始listener + listener net.Listener // 接口化封装的listener isHttps bool shutdownChan chan bool } @@ -40,6 +35,7 @@ func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer { httpServer : s.newHttpServer(addr), shutdownChan : make(chan bool), } + // 是否有继承的文件描述符 if len(fd) > 0 && fd[0] > 0 { gs.fd = uintptr(fd[0]) } @@ -65,19 +61,15 @@ func (s *gracefulServer) ListenAndServe() error { if err != nil { return err } - //file, err := ln.(*net.TCPListener).File() - //if err != nil { - // return err - //} - //s.fd = file.Fd() - s.listener = ln + s.listener = ln + s.rawListener = ln return s.doServe() } // 获得文件描述符 func (s *gracefulServer) Fd() uintptr { - if s.listener != nil { - file, err := s.listener.(*net.TCPListener).File() + if s.rawListener != nil { + file, err := s.rawListener.(*net.TCPListener).File() if err == nil { return file.Fd() } @@ -110,13 +102,9 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error { if err != nil { return err } - //file, err := ln.(*net.TCPListener).File() - //if err != nil { - // return err - //} - //s.fd = file.Fd() - s.listener = tls.NewListener(ln, config) - s.isHttps = true + + s.listener = tls.NewListener(ln, config) + s.rawListener = ln return s.doServe() } @@ -131,7 +119,11 @@ func (s *gracefulServer) getProto() string { // 开始执行Web Server服务处理 func (s *gracefulServer) doServe() error { - glog.Printfln("%d: %s server started listening on [%s]", gproc.Pid(), s.getProto(), s.addr) + action := "started" + if s.fd != 0 { + action = "reloaded" + } + glog.Printfln("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr) err := s.httpServer.Serve(s.listener) <-s.shutdownChan return err diff --git a/g/os/gproc/gproc.go b/g/os/gproc/gproc.go index 3b6deaa9b..8c435a8af 100644 --- a/g/os/gproc/gproc.go +++ b/g/os/gproc/gproc.go @@ -31,14 +31,17 @@ func Pid() int { return os.Getpid() } -// 获取父进程ID(gproc父进程,不存在时则使用系统父进程) +// 获取父进程ID(gproc父进程,如果当前进程本身就是父进程,那么返回自身的pid,不存在时则使用系统父进程) func PPid() int { + if !IsChild() { + return Pid() + } // gPROC_ENV_KEY_PPID_KEY为gproc包自定义的父进程 ppidValue := os.Getenv(gPROC_ENV_KEY_PPID_KEY) if ppidValue != "" { return gconv.Int(ppidValue) } - return os.Getppid() + return PPidOS() } // 获取父进程ID(系统父进程) diff --git a/geg/net/ghttp/https/https_http.go b/geg/net/ghttp/https/https_http.go index 4440453d0..3a42b01a7 100644 --- a/geg/net/ghttp/https/https_http.go +++ b/geg/net/ghttp/https/https_http.go @@ -11,7 +11,8 @@ func main() { r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!") }) s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") - s.SetHTTPSPort(8198) - s.SetPort(8199) + s.SetHTTPSPort(8198, 8199) + s.SetPort(8200, 8300) + s.EnableAdmin() s.Run() } \ No newline at end of file diff --git a/geg/net/ghttp/hot_restart/admin.go b/geg/net/ghttp/reload/admin.go similarity index 100% rename from geg/net/ghttp/hot_restart/admin.go rename to geg/net/ghttp/reload/admin.go diff --git a/geg/net/ghttp/reload/https_http.go b/geg/net/ghttp/reload/https_http.go new file mode 100644 index 000000000..3a42b01a7 --- /dev/null +++ b/geg/net/ghttp/reload/https_http.go @@ -0,0 +1,18 @@ +package main + +import ( + "gitee.com/johng/gf/g/net/ghttp" +) + +func main() { + s := ghttp.GetServer() + s.EnableAdmin() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!") + }) + s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") + s.SetHTTPSPort(8198, 8199) + s.SetPort(8200, 8300) + s.EnableAdmin() + s.Run() +} \ No newline at end of file diff --git a/geg/net/ghttp/hot_restart/multi_port.go b/geg/net/ghttp/reload/multi_port.go similarity index 100% rename from geg/net/ghttp/hot_restart/multi_port.go rename to geg/net/ghttp/reload/multi_port.go diff --git a/geg/net/ghttp/hot_restart/multi_port_and_server.go b/geg/net/ghttp/reload/multi_port_and_server.go similarity index 100% rename from geg/net/ghttp/hot_restart/multi_port_and_server.go rename to geg/net/ghttp/reload/multi_port_and_server.go diff --git a/geg/net/ghttp/hot_restart/simple.go b/geg/net/ghttp/reload/simple.go similarity index 100% rename from geg/net/ghttp/hot_restart/simple.go rename to geg/net/ghttp/reload/simple.go From 5856605fc3e9135c85747a99ad814bb48566e8b3 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 May 2018 22:32:56 +0800 Subject: [PATCH 27/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E6=9C=BA=E5=88=B6=E5=9C=A8Windows=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 15 ----- g/net/ghttp/ghttp_server_admin.go | 79 ++++++++++++++++++++++++-- g/net/ghttp/ghttp_server_comm_child.go | 2 +- g/net/ghttp/ghttp_server_comm_main.go | 4 +- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 773155342..ed423a195 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -321,21 +321,6 @@ func (s *Server) startServer(fdMap listenerFdMap) { s.status = 1 } -// 平滑重启Web Server -func (s *Server) Reload() { - sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) -} - -// 完整重启Web Server -func (s *Server) Restart() { - sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) -} - -// 关闭Web Server -func (s *Server) Shutdown() { - sendProcessMsg(gproc.PPid(), gMSG_SHUTDOWN, nil) -} - // 获取当前监听的文件描述符信息,构造成map返回 func (s *Server) getListenerFdMap() map[string]string { m := map[string]string { diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go index 11c09ea9c..bc785c184 100644 --- a/g/net/ghttp/ghttp_server_admin.go +++ b/g/net/ghttp/ghttp_server_admin.go @@ -11,11 +11,27 @@ import ( "strings" "gitee.com/johng/gf/g/os/gview" "runtime" + "gitee.com/johng/gf/g/os/gproc" + "sync" + "gitee.com/johng/gf/g/os/gtime" + "errors" + "fmt" + "gitee.com/johng/gf/g/container/gtype" +) + +const ( + gADMIN_ACTION_INTERVAL_LIMIT = 0 // (毫秒)每一次执行管理操作的间隔限制 ) // 用于服务管理的对象 type utilAdmin struct {} +// (进程级别)用于Web Server管理操作的互斥锁,保证管理操作的原子性 +var serverActionLocker sync.Mutex + +// (进程级别)用于记录上一次操作的时间(毫秒) +var serverActionLastTime = gtype.NewInt64(gtime.Millisecond()) + // 服务管理首页 func (p *utilAdmin) Index(r *Request) { data := map[string]interface{}{ @@ -41,21 +57,31 @@ func (p *utilAdmin) Reload(r *Request) { if runtime.GOOS == "windows" { p.Restart(r) } else { - r.Response.Write("reload server") - r.Server.Reload() + if err := r.Server.Reload(); err == nil { + r.Response.Write("server reloaded") + } else { + r.Response.Write(err.Error()) + } } } // 服务完整重启 func (p *utilAdmin) Restart(r *Request) { - r.Response.Write("restart server") - r.Server.Restart() + if err := r.Server.Restart(); err == nil { + r.Response.Write("server restarted") + } else { + r.Response.Write(err.Error()) + } } // 服务关闭 func (p *utilAdmin) Shutdown(r *Request) { - r.Response.Write("shutdown server") r.Server.Shutdown() + if err := r.Server.Shutdown(); err == nil { + r.Response.Write("server shutdown") + } else { + r.Response.Write(err.Error()) + } } @@ -66,4 +92,47 @@ func (s *Server) EnableAdmin(pattern...string) { p = pattern[0] } s.BindObject(p, &utilAdmin{}) +} + +// 平滑重启Web Server +func (s *Server) Reload() error { + serverActionLocker.Lock() + defer serverActionLocker.Unlock() + if err := s.checkActionFrequence(); err != nil { + return err + } + sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) + return nil +} + +// 完整重启Web Server +func (s *Server) Restart() error { + serverActionLocker.Lock() + defer serverActionLocker.Unlock() + if err := s.checkActionFrequence(); err != nil { + return err + } + sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) + return nil +} + +// 关闭Web Server +func (s *Server) Shutdown() error { + serverActionLocker.Lock() + defer serverActionLocker.Unlock() + if err := s.checkActionFrequence(); err != nil { + return err + } + sendProcessMsg(gproc.PPid(), gMSG_SHUTDOWN, nil) + return nil +} + +// 检测当前操作的频繁度 +func (s *Server) checkActionFrequence() error { + interval := gtime.Millisecond() - serverActionLastTime.Val() + if interval < gADMIN_ACTION_INTERVAL_LIMIT { + return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT - interval)) + } + serverActionLastTime.Set(gtime.Millisecond()) + return nil } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index 5938c1c1d..d9362eddb 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -24,7 +24,7 @@ const ( gPROC_CHILD_MAX_IDLE_TIME = 3000 // 子进程闲置时间(未开启心跳机制的时间) ) -// 心跳消息 +// 心跳处理(方法为空,逻辑放到公共通信switch中进行处理) func onCommChildHeartbeat(pid int, data []byte) { } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 04fa6a8ed..3ce65e742 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -26,9 +26,9 @@ func onCommMainStart(pid int, data []byte) { sendProcessMsg(p.Pid(), gMSG_START, nil) } -// 心跳处理 +// 心跳处理(方法为空,逻辑放到公共通信switch中进行处理) func onCommMainHeartbeat(pid int, data []byte) { - updateProcessCommTime(pid) + } // 平滑重启服务 From cc70bbf6a2b581960c67bb4e292b6bc87e810439 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 May 2018 23:42:02 +0800 Subject: [PATCH 28/31] =?UTF-8?q?ghttp.Server=E7=83=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E5=9C=A8windows=E7=B3=BB=E7=BB=9F=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E7=A8=B3=E5=AE=9A=E6=80=A7=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 6 +----- g/net/ghttp/ghttp_server_admin.go | 4 ++++ g/net/ghttp/ghttp_server_comm.go | 7 ++++--- g/net/ghttp/ghttp_server_comm_main.go | 15 ++++++++++++--- g/os/gproc/gproc_comm.go | 4 ---- geg/other/test.go | 7 +------ 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index ed423a195..32c9373a0 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -127,11 +127,7 @@ func init() { doneChan <- struct{}{} if !gproc.IsChild() { - if serverMapping.Size() > 1 { - glog.Printfln("%d: all web servers shutdown", gproc.Pid()) - } else { - glog.Printfln("%d: web server shutdown", gproc.Pid()) - } + glog.Printfln("%d: all web servers shutdown", gproc.Pid()) } }() } diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go index bc785c184..d80abc7bb 100644 --- a/g/net/ghttp/ghttp_server_admin.go +++ b/g/net/ghttp/ghttp_server_admin.go @@ -17,6 +17,7 @@ import ( "errors" "fmt" "gitee.com/johng/gf/g/container/gtype" + "gitee.com/johng/gf/g/os/glog" ) const ( @@ -101,6 +102,7 @@ func (s *Server) Reload() error { if err := s.checkActionFrequence(); err != nil { return err } + glog.Printfln("%d: server reloading", gproc.Pid()) sendProcessMsg(gproc.Pid(), gMSG_RELOAD, nil) return nil } @@ -112,6 +114,7 @@ func (s *Server) Restart() error { if err := s.checkActionFrequence(); err != nil { return err } + glog.Printfln("%d: server restarting", gproc.Pid()) sendProcessMsg(gproc.Pid(), gMSG_RESTART, nil) return nil } @@ -123,6 +126,7 @@ func (s *Server) Shutdown() error { if err := s.checkActionFrequence(); err != nil { return err } + glog.Printfln("%d: server shutting down", gproc.Pid()) sendProcessMsg(gproc.PPid(), gMSG_SHUTDOWN, nil) return nil } diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 57b78439d..92dcff786 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -15,6 +15,7 @@ import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gtime" + "fmt" ) const ( @@ -34,7 +35,7 @@ const ( var procSignalChan = make(chan os.Signal) // 上一次进程间心跳的时间戳 -var lastUpdateTime = gtype.NewInt() +var lastUpdateTime = gtype.NewInt(int(gtime.Millisecond())) // (主子进程)在第一次创建子进程成功之后才会开始心跳检测,同理对应超时时间才会生效 var checkHeartbeat = gtype.NewBool() @@ -56,8 +57,8 @@ func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { // 记录消息日志,用于调试 - //content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) - //fmt.Print(content) + content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) + fmt.Print(content) //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 3ce65e742..206a73ac3 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -14,6 +14,8 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/os/gproc" + "fmt" + "gitee.com/johng/gf/g/os/glog" ) // (主进程)主进程与子进程上一次活跃时间映射map @@ -22,7 +24,11 @@ var procUpdateTimeMap = gmap.NewIntIntMap() // 开启服务 func onCommMainStart(pid int, data []byte) { p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - p.Start() + if _, err := p.Start(); err != nil { + glog.Errorfln("%d: fork new process error:%s", gproc.Pid(), err.Error()) + return + } + updateProcessCommTime(p.Pid()) sendProcessMsg(p.Pid(), gMSG_START, nil) } @@ -38,7 +44,7 @@ func onCommMainReload(pid int, data []byte) { // 完整重启服务 func onCommMainRestart(pid int, data []byte) { - // 如果是父进程接收到重启指令,那么通知所有子进程重启 + // 如果是父进程自身发送的重启指令,那么通知所有子进程重启 if pid == gproc.Pid() { procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) return @@ -77,7 +83,9 @@ func handleMainProcessHeartbeat() { // 清理过期进程 if checkHeartbeat.Val() { for _, pid := range procManager.Pids() { - if int(gtime.Millisecond()) - procUpdateTimeMap.Get(pid) > gPROC_HEARTBEAT_TIMEOUT { + updatetime := procUpdateTimeMap.Get(pid) + if updatetime > 0 && int(gtime.Millisecond()) - updatetime > gPROC_HEARTBEAT_TIMEOUT { + fmt.Println("remove pid", pid, int(gtime.Millisecond()), updatetime) // 这里需要手动从进程管理器中去掉该进程 procManager.RemoveProcess(pid) sendProcessMsg(pid, gMSG_CLOSE, nil) @@ -86,6 +94,7 @@ func handleMainProcessHeartbeat() { } // (双保险)如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ + //glog.Printfln("%d: all children died, exit", gproc.Pid()) os.Exit(0) } } diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 027895574..82cafe386 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -122,10 +122,6 @@ func Receive() *Msg { // 向指定gproc进程发送数据 // 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) func Send(pid int, data interface{}) error { - // 首先检测进程存在不存在,存在才能发送消息 - if _, err := os.FindProcess(pid); err != nil { - return err - } buffer := gconv.Bytes(data) b := make([]byte, 0) b = append(b, gbinary.EncodeInt32(int32(len(buffer) + 12))...) diff --git a/geg/other/test.go b/geg/other/test.go index 57b793a10..d60cd94be 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -6,8 +6,6 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gproc" - "os" - "syscall" ) // 数据解包,防止黏包 @@ -45,10 +43,7 @@ func checksum(buffer []byte) uint32 { } func main(){ - p, _ := os.FindProcess(10354) - fmt.Println(p.Signal(syscall.Signal(1))) - return - b := gfile.GetBinContents("/tmp/gproc/30588") + b := gfile.GetBinContents("/home/john/Documents/11248") for _, msg := range bufferToMsgs(b) { fmt.Println(msg.Pid) fmt.Println(msg.Data) From 29d72cc00e6f442d25280a30c7ea81fcb1759eab Mon Sep 17 00:00:00 2001 From: John Date: Thu, 17 May 2018 17:10:19 +0800 Subject: [PATCH 29/31] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=9C=A8=E6=9F=90=E4=BA=9B=E7=B3=BB=E7=BB=9F=E4=B8=8Agfsnotify?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server_comm.go | 7 ++-- g/net/ghttp/ghttp_server_comm_child.go | 2 +- g/net/ghttp/ghttp_server_comm_main.go | 9 +++-- g/os/gfile/gfile.go | 13 +++++- g/os/gfsnotify/gfsnotify.go | 41 ++++++++++++++----- g/os/gfsnotify/gfsnotify_check.go | 55 ++++++++++++++++++++++++++ g/os/gproc/gproc_comm.go | 52 +++++++++++++++++++++++- geg/os/gflock/gflock.go | 20 ++++++++-- geg/other/test.go | 7 ++++ 9 files changed, 182 insertions(+), 24 deletions(-) create mode 100644 g/os/gfsnotify/gfsnotify_check.go diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 92dcff786..060ae499a 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -16,6 +16,7 @@ import ( "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gtime" "fmt" + "gitee.com/johng/gf/g/os/glog" ) const ( @@ -58,7 +59,7 @@ func handleProcessMsg() { if msg := gproc.Receive(); msg != nil { // 记录消息日志,用于调试 content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) - fmt.Print(content) + glog.Print(content) //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] @@ -101,8 +102,8 @@ func handleProcessMsg() { } // 向进程发送操作消息 -func sendProcessMsg(pid int, act int, data []byte) { - gproc.Send(pid, formatMsgBuffer(act, data)) +func sendProcessMsg(pid int, act int, data []byte) error { + return gproc.Send(pid, formatMsgBuffer(act, data)) } // 生成一条满足Web Server进程通信协议的消息 diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index d9362eddb..ac40dc704 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -96,7 +96,7 @@ func handleChildProcessHeartbeat() { // 超过时间没有接收到主进程心跳,自动关闭退出 if checkHeartbeat.Val() && (int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT) { // 子进程有时会无法退出(僵尸?),这里直接使用exit,而不是return - //glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastHeartbeatTime.Val(), gPROC_HEARTBEAT_TIMEOUT) + glog.Printfln("%d: %d - %d > %d", gproc.Pid(), int(gtime.Millisecond()), lastUpdateTime.Val(), gPROC_HEARTBEAT_TIMEOUT) glog.Printfln("%d: heartbeat timeout[%dms], exit", gproc.Pid(), gPROC_HEARTBEAT_TIMEOUT) os.Exit(0) } diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index 206a73ac3..a1bafa766 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -10,12 +10,12 @@ package ghttp import ( "os" - "time" - "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/container/gmap" - "gitee.com/johng/gf/g/os/gproc" "fmt" + "time" "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/os/gproc" + "gitee.com/johng/gf/g/container/gmap" ) // (主进程)主进程与子进程上一次活跃时间映射map @@ -29,6 +29,7 @@ func onCommMainStart(pid int, data []byte) { return } updateProcessCommTime(p.Pid()) + // 子进程创建成功之后再发送执行命令 sendProcessMsg(p.Pid(), gMSG_START, nil) } diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index 2f4176039..286571bdc 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -111,7 +111,7 @@ func Info(path string) *os.FileInfo { return &info } -// 修改时间 +// 修改时间(秒) func MTime(path string) int64 { f, e := os.Stat(path) if e != nil { @@ -120,6 +120,17 @@ func MTime(path string) int64 { return f.ModTime().Unix() } +// 修改时间(毫秒) +func MTimeMillisecond(path string) int64 { + f, e := os.Stat(path) + if e != nil { + return 0 + } + seconds := f.ModTime().Unix() + nanoSeconds := f.ModTime().Nanosecond() + return seconds*1000 + int64(nanoSeconds/1000000) +} + // 文件大小(bytes) func Size(path string) int64 { f, e := os.Stat(path) diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index cbed56312..8152721ac 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -6,6 +6,9 @@ // 文件监控. // 使用时需要注意的是,一旦一个文件被删除,那么对其的监控将会失效。 +// 特点: +// 1、底层使用了fsnotify机制作为异步监听插件; +// 2、(可选)文件主动自动检查作为fsnotify文件监听的辅助手段来保障监听文件如果发生改变,监控端将会及时收到提醒(解决某些业务场景下的fsnotify延迟问题); package gfsnotify import ( @@ -17,14 +20,18 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/glist" "gitee.com/johng/gf/g/container/gqueue" + "gitee.com/johng/gf/g/container/gtype" + "gitee.com/johng/gf/g/os/gtime" ) // 监听管理对象 type Watcher struct { - watcher *fsnotify.Watcher // 底层fsnotify对象 - events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 - closeChan chan struct{} // 关闭事件 - callbacks *gmap.StringInterfaceMap // 监听的回调函数 + watcher *fsnotify.Watcher // 底层fsnotify对象 + events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 + closeChan chan struct{} // 关闭事件 + callbacks *gmap.StringInterfaceMap // 监听的回调函数 + watchUpdateTimeMap *gmap.StringIntMap // (毫秒)监控文件最新的通知时间 + activeCheckInterval *gtype.Int // (毫秒)主动文件检查时间间隔 } // 监听事件对象 @@ -67,19 +74,32 @@ func Remove(path string) error { func New() (*Watcher, error) { if watch, err := fsnotify.NewWatcher(); err == nil { w := &Watcher { - watcher : watch, - events : gqueue.New(), - closeChan : make(chan struct{}, 1), - callbacks : gmap.NewStringInterfaceMap(), + watcher : watch, + events : gqueue.New(), + closeChan : make(chan struct{}, 1), + callbacks : gmap.NewStringInterfaceMap(), + watchUpdateTimeMap : gmap.NewStringIntMap(), + activeCheckInterval : gtype.NewInt(), } w.startWatchLoop() w.startEventLoop() + w.startActiveCheckLoop() return w, nil } else { return nil, err } } +// 启动主动文件更新检测机制 +func (w *Watcher) EnableActiveCheck(interval int) { + w.activeCheckInterval.Set(interval) +} + +// 关闭主动文件更新检测机制 +func (w *Watcher) DisableActiveCheck() { + w.activeCheckInterval.Set(0) +} + // 关闭监听管理对象 func (w *Watcher) Close() { w.watcher.Close() @@ -108,6 +128,8 @@ func (w *Watcher) Add(path string, callback func(event *Event)) error { }) // 添加底层监听 w.watcher.Add(path) + // 添加默认更新时间 + w.watchUpdateTimeMap.Set(path, int(gfile.MTimeMillisecond(path))) return nil } @@ -117,7 +139,7 @@ func (w *Watcher) Remove(path string) error { return w.watcher.Remove(path) } -// 监听循环 +// fsnotify监听循环 func (w *Watcher) startWatchLoop() { go func() { for { @@ -151,6 +173,7 @@ func (w *Watcher) startEventLoop() { w.watcher.Add(event.Path) continue } + w.watchUpdateTimeMap.Set(event.Path, int(gtime.Millisecond())) if l := w.callbacks.Get(event.Path); l != nil { grpool.Add(func() { for _, v := range l.(*glist.List).FrontAll() { diff --git a/g/os/gfsnotify/gfsnotify_check.go b/g/os/gfsnotify/gfsnotify_check.go new file mode 100644 index 000000000..7aae8acf1 --- /dev/null +++ b/g/os/gfsnotify/gfsnotify_check.go @@ -0,0 +1,55 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// 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://gitee.com/johng/gf. + +package gfsnotify + +import ( + "time" + "gitee.com/johng/gf/g/os/gtime" + "gitee.com/johng/gf/g/os/gfile" + "fmt" +) + +const ( + gDEFAULT_ACTIVE_CHECK_INTERVAL = 500 // (毫秒)默认主动检测时间间隔,未开启时什么也不做 +) + +// 文件定时监听器循环 +func (w *Watcher) startActiveCheckLoop() { + go func() { + for { + select { + // 关闭事件 + case <- w.closeChan: + return + + default: + if w.activeCheckInterval.Val() > 0 { + paths := w.watchUpdateTimeMap.Keys() + for _, path := range paths { + lastUpdateTime := w.watchUpdateTimeMap.Get(path) + if int(gtime.Millisecond()) - lastUpdateTime > w.activeCheckInterval.Val() { + + fileUpdateTime := int(gfile.MTimeMillisecond(path)) + fmt.Println("check:", path, fileUpdateTime, lastUpdateTime) + if fileUpdateTime > lastUpdateTime { + fmt.Println("update:", path) + w.watchUpdateTimeMap.Set(path, fileUpdateTime) + w.events.PushBack(&Event{ + Path : path, + Op : Op(WRITE), + }) + } + } + } + time.Sleep(time.Duration(w.activeCheckInterval.Val())*time.Millisecond) + } else { + time.Sleep(gDEFAULT_ACTIVE_CHECK_INTERVAL*time.Millisecond) + } + } + } + }() +} \ No newline at end of file diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 82cafe386..8c1d261e7 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -18,6 +18,8 @@ import ( "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gtime" + "io" + "errors" ) const ( @@ -25,6 +27,8 @@ const ( gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" // 自动通信文件清理时间间隔 gPROC_COMM_AUTO_CLEAR_INTERVAL = time.Second + // 写入通信数据失败时候的重试次数 + gPROC_COMM_FAILURE_RETRY_COUNT = 3 ) // 全局通信文件清理文件锁(同一时刻只能存在一个进程进行通信文件清理) @@ -33,6 +37,8 @@ var commClearLocker = gflock.New("comm.clear.lock") var commLocker = gflock.New(fmt.Sprintf("%d.lock", os.Getpid())) // 进程通信消息队列 var commQueue = gqueue.New() +// 文件监控器 +var watcher, _ = gfsnotify.New() // TCP通信数据结构定义 type Msg struct { @@ -67,8 +73,10 @@ func init() { os.Truncate(path, 0) commLocker.UnLock() } + watcher.EnableActiveCheck(1000) // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 - err := gfsnotify.Add(path, func(event *gfsnotify.Event) { + err := watcher.Add(path, func(event *gfsnotify.Event) { + glog.Printfln("%d: gfsnotify", Pid()) checkCommBuffer(path) }) if err != nil { @@ -122,6 +130,7 @@ func Receive() *Msg { // 向指定gproc进程发送数据 // 数据格式:总长度(32bit) | PID(32bit) | 校验(32bit) | 参数(变长) func Send(pid int, data interface{}) error { + var err error = nil buffer := gconv.Bytes(data) b := make([]byte, 0) b = append(b, gbinary.EncodeInt32(int32(len(buffer) + 12))...) @@ -130,11 +139,50 @@ func Send(pid int, data interface{}) error { b = append(b, buffer...) l := gflock.New(fmt.Sprintf("%d.lock", pid)) l.Lock() - err := gfile.PutBinContentsAppend(getCommFilePath(pid), b) + for i := gPROC_COMM_FAILURE_RETRY_COUNT; i > 0; i-- { + err = doSend(pid, b) + if err == nil { + break + } + } l.UnLock() + glog.Printfln("%d to %d, %v, %d, %v", Pid(), pid, data, gfile.Size(getCommFilePath(pid)), err) return err } +// 执行进程间通信数据写入 +func doSend(pid int, buffer []byte) error { + file, err := gfile.OpenWithFlag(getCommFilePath(pid), os.O_RDWR|os.O_CREATE|os.O_APPEND) + if err != nil{ + return err + } + // 获取原有文件内容大小 + stat, err := file.Stat() + if err != nil { + return err + } + oldSize := stat.Size() + // 执行数据写入 + writeSize, err := file.Write(buffer) + if err != nil { + return err + } + if writeSize < len(buffer) { + return io.ErrShortWrite + } + // 写入成功之后获取最新文件内容大小,执行对比 + if stat, err := file.Stat(); err != nil { + return err + } else { + // 由于文件锁机制的保证,同一时刻只会有一个进程(&协程)在执行写入,不会出现数据粘包情况 + // 这里从严谨性考虑增加大小判断,更进一步避免粘包,或者丢包情况 + if stat.Size() - int64(writeSize) != oldSize { + return errors.New("error writing data") + } + } + return nil +} + // 获取指定进程的通信文件地址 func getCommFilePath(pid int) string { return getCommDirPath() + gfile.Separator + gconv.String(pid) diff --git a/geg/os/gflock/gflock.go b/geg/os/gflock/gflock.go index 2ceac44f0..2b5e3256b 100644 --- a/geg/os/gflock/gflock.go +++ b/geg/os/gflock/gflock.go @@ -6,12 +6,24 @@ import ( "time" ) -func main() { +func test() { l := gflock.New("1.lock") fmt.Println(l.Path()) - fmt.Println(l.TryLock()) - fmt.Println("lock 1") l.Lock() fmt.Println("lock 1") - time.Sleep(time.Hour) + l.Lock() + fmt.Println("lock 2") +} + +func active() { + i := 0 + for { + time.Sleep(time.Second) + i++ + } +} + +func main() { + go active() + test() } diff --git a/geg/other/test.go b/geg/other/test.go index d60cd94be..63d854de7 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -6,6 +6,7 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gproc" + "os" ) // 数据解包,防止黏包 @@ -43,6 +44,12 @@ func checksum(buffer []byte) uint32 { } func main(){ + f, _ := os.Stat("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") + fmt.Println(f.ModTime().Unix()) + fmt.Println(f.ModTime().Nanosecond()) + fmt.Println(gfile.MTimeMillisecond("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go")) + + return b := gfile.GetBinContents("/home/john/Documents/11248") for _, msg := range bufferToMsgs(b) { fmt.Println(msg.Pid) From 57f3f2b39dcb847f5073969c77791e303a2444c0 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 17 May 2018 22:32:50 +0800 Subject: [PATCH 30/31] =?UTF-8?q?=E5=AE=8C=E6=88=90ghttp.Server=E7=83=AD?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E6=9C=BA=E5=88=B6=E5=9C=A8Linux=5Famd64?= =?UTF-8?q?=E5=8F=8AWindows=5Famd64=E4=B8=8B=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- g/net/ghttp/ghttp_server.go | 2 +- g/net/ghttp/ghttp_server_admin.go | 33 ++++++++- g/net/ghttp/ghttp_server_comm.go | 6 +- g/net/ghttp/ghttp_server_comm_child.go | 2 +- .../ghttp/ghttp_server_comm_child_windows.go | 7 -- g/net/ghttp/ghttp_server_comm_main.go | 47 +++++++++---- g/os/gfsnotify/gfsnotify.go | 41 +++-------- g/os/gfsnotify/gfsnotify_check.go | 55 --------------- g/os/gproc/gproc_comm.go | 44 ++++++++---- geg/other/test.go | 68 ++++--------------- 10 files changed, 121 insertions(+), 184 deletions(-) delete mode 100644 g/os/gfsnotify/gfsnotify_check.go diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 32c9373a0..84810816a 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -127,7 +127,7 @@ func init() { doneChan <- struct{}{} if !gproc.IsChild() { - glog.Printfln("%d: all web servers shutdown", gproc.Pid()) + glog.Printfln("%d: all servers shutdown", gproc.Pid()) } }() } diff --git a/g/net/ghttp/ghttp_server_admin.go b/g/net/ghttp/ghttp_server_admin.go index d80abc7bb..fbee04ea1 100644 --- a/g/net/ghttp/ghttp_server_admin.go +++ b/g/net/ghttp/ghttp_server_admin.go @@ -21,7 +21,7 @@ import ( ) const ( - gADMIN_ACTION_INTERVAL_LIMIT = 0 // (毫秒)每一次执行管理操作的间隔限制 + gADMIN_ACTION_INTERVAL_LIMIT = 3000 // (毫秒)服务开启后允许执行管理操作的间隔限制 ) // 用于服务管理的对象 @@ -33,6 +33,12 @@ var serverActionLocker sync.Mutex // (进程级别)用于记录上一次操作的时间(毫秒) var serverActionLastTime = gtype.NewInt64(gtime.Millisecond()) +// 当前服务进程所处的互斥管理操作状态 +// 1 : reload +// 2 : restart +// 4 : shutdown +var serverProcessStatus = gtype.NewInt() + // 服务管理首页 func (p *utilAdmin) Index(r *Request) { data := map[string]interface{}{ @@ -99,6 +105,9 @@ func (s *Server) EnableAdmin(pattern...string) { func (s *Server) Reload() error { serverActionLocker.Lock() defer serverActionLocker.Unlock() + if err := s.checkActionStatus(); err != nil { + return err + } if err := s.checkActionFrequence(); err != nil { return err } @@ -111,6 +120,9 @@ func (s *Server) Reload() error { func (s *Server) Restart() error { serverActionLocker.Lock() defer serverActionLocker.Unlock() + if err := s.checkActionStatus(); err != nil { + return err + } if err := s.checkActionFrequence(); err != nil { return err } @@ -123,6 +135,9 @@ func (s *Server) Restart() error { func (s *Server) Shutdown() error { serverActionLocker.Lock() defer serverActionLocker.Unlock() + if err := s.checkActionStatus(); err != nil { + return err + } if err := s.checkActionFrequence(); err != nil { return err } @@ -139,4 +154,20 @@ func (s *Server) checkActionFrequence() error { } serverActionLastTime.Set(gtime.Millisecond()) return nil +} + +// 检查当前服务进程的状态 +func (s *Server) checkActionStatus() error { + status := serverProcessStatus.Val() + if status > 0 { + switch status { + case 1: + return errors.New("server is reloading") + case 2: + return errors.New("server is restarting") + case 4: + return errors.New("server is shutting down") + } + } + return nil } \ No newline at end of file diff --git a/g/net/ghttp/ghttp_server_comm.go b/g/net/ghttp/ghttp_server_comm.go index 060ae499a..b8b6f292a 100644 --- a/g/net/ghttp/ghttp_server_comm.go +++ b/g/net/ghttp/ghttp_server_comm.go @@ -15,8 +15,6 @@ import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/encoding/gbinary" "gitee.com/johng/gf/g/os/gtime" - "fmt" - "gitee.com/johng/gf/g/os/glog" ) const ( @@ -58,8 +56,8 @@ func handleProcessMsg() { for { if msg := gproc.Receive(); msg != nil { // 记录消息日志,用于调试 - content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) - glog.Print(content) + //content := gconv.String(msg.Pid) + "=>" + gconv.String(gproc.Pid()) + ":" + fmt.Sprintf("%v\n", msg.Data) + //glog.Print(content) //gfile.PutContentsAppend("/tmp/gproc-log", content) act := gbinary.DecodeToUint(msg.Data[0 : 1]) data := msg.Data[1 : ] diff --git a/g/net/ghttp/ghttp_server_comm_child.go b/g/net/ghttp/ghttp_server_comm_child.go index ac40dc704..bd60638ce 100644 --- a/g/net/ghttp/ghttp_server_comm_child.go +++ b/g/net/ghttp/ghttp_server_comm_child.go @@ -21,7 +21,7 @@ import ( ) const ( - gPROC_CHILD_MAX_IDLE_TIME = 3000 // 子进程闲置时间(未开启心跳机制的时间) + gPROC_CHILD_MAX_IDLE_TIME = 10000 // 子进程闲置时间(未开启心跳机制的时间) ) // 心跳处理(方法为空,逻辑放到公共通信switch中进行处理) diff --git a/g/net/ghttp/ghttp_server_comm_child_windows.go b/g/net/ghttp/ghttp_server_comm_child_windows.go index fc1bf9463..a28027441 100644 --- a/g/net/ghttp/ghttp_server_comm_child_windows.go +++ b/g/net/ghttp/ghttp_server_comm_child_windows.go @@ -8,19 +8,12 @@ package ghttp import ( "gitee.com/johng/gf/g/os/gproc" - "os" ) // 开启所有Web Server(根据消息启动) func onCommChildStart(pid int, data []byte) { // 进程创建成功之后(开始执行服务时间点为准),通知主进程自身的存在,并开始执行心跳机制 sendProcessMsg(gproc.PPid(), gMSG_NEW_FORK, nil) - // 如果创建自己的父进程非gproc父进程,那么表示该进程为重启创建的进程,创建成功之后需要通知父进程自行销毁 - if gproc.PPidOS() != gproc.PPid() { - if p, err := os.FindProcess(gproc.PPidOS()); err == nil { - p.Kill() - } - } // 开启Web Server服务 serverMapping.RLockFunc(func(m map[string]interface{}) { for _, v := range m { diff --git a/g/net/ghttp/ghttp_server_comm_main.go b/g/net/ghttp/ghttp_server_comm_main.go index a1bafa766..7b9b1e4a6 100644 --- a/g/net/ghttp/ghttp_server_comm_main.go +++ b/g/net/ghttp/ghttp_server_comm_main.go @@ -10,7 +10,6 @@ package ghttp import ( "os" - "fmt" "time" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gtime" @@ -23,14 +22,13 @@ var procUpdateTimeMap = gmap.NewIntIntMap() // 开启服务 func onCommMainStart(pid int, data []byte) { - p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) - if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork new process error:%s", gproc.Pid(), err.Error()) - return + fork := forkNewProcess() + if fork == nil { + os.Exit(1) } - updateProcessCommTime(p.Pid()) + updateProcessCommTime(fork.Pid()) // 子进程创建成功之后再发送执行命令 - sendProcessMsg(p.Pid(), gMSG_START, nil) + sendProcessMsg(fork.Pid(), gMSG_START, nil) } // 心跳处理(方法为空,逻辑放到公共通信switch中进行处理) @@ -50,12 +48,19 @@ func onCommMainRestart(pid int, data []byte) { procManager.Send(formatMsgBuffer(gMSG_RESTART, nil)) return } - // 否则杀掉子进程,然后新建一个完整的子进程 + // 首先创建子进程,暂时不开始服务,否则会有端口冲突 + fork := forkNewProcess() + if fork == nil { + os.Exit(1) + } + // 然后通知旧的子进程自动关闭并退出(不需要新建的子进程来处理) + sendProcessMsg(pid, gMSG_CLOSE, nil) if p, err := os.FindProcess(pid); err == nil && p != nil { p.Kill() p.Wait() } - sendProcessMsg(gproc.Pid(), gMSG_START, nil) + // 通知新的子进程执行服务监听 + sendProcessMsg(fork.Pid(), gMSG_START, nil) } // 新建子进程通知 @@ -76,6 +81,16 @@ func updateProcessCommTime(pid int) { procUpdateTimeMap.Set(pid, int(gtime.Millisecond())) } +// 创建一个子进程,但是暂时不执行服务监听 +func forkNewProcess() *gproc.Process { + p := procManager.NewProcess(os.Args[0], os.Args, os.Environ()) + if _, err := p.Start(); err != nil { + glog.Errorfln("%d: fork new process error:%s", gproc.Pid(), err.Error()) + return nil + } + return p +} + // 主进程与子进程相互异步方式发送心跳信息,保持活跃状态 func handleMainProcessHeartbeat() { for { @@ -86,17 +101,19 @@ func handleMainProcessHeartbeat() { for _, pid := range procManager.Pids() { updatetime := procUpdateTimeMap.Get(pid) if updatetime > 0 && int(gtime.Millisecond()) - updatetime > gPROC_HEARTBEAT_TIMEOUT { - fmt.Println("remove pid", pid, int(gtime.Millisecond()), updatetime) + //fmt.Println("remove pid", pid, int(gtime.Millisecond()), updatetime) // 这里需要手动从进程管理器中去掉该进程 procManager.RemoveProcess(pid) sendProcessMsg(pid, gMSG_CLOSE, nil) } } + + // (双保险)如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 + if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ + glog.Printfln("%d: all children died, exit", gproc.Pid()) + os.Exit(0) + } } - // (双保险)如果所有子进程都退出,并且主进程未活动达到超时时间,那么主进程也没存在的必要 - if procManager.Size() == 0 && int(gtime.Millisecond()) - lastUpdateTime.Val() > gPROC_HEARTBEAT_TIMEOUT{ - //glog.Printfln("%d: all children died, exit", gproc.Pid()) - os.Exit(0) - } + } } diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index 8152721ac..cbed56312 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -6,9 +6,6 @@ // 文件监控. // 使用时需要注意的是,一旦一个文件被删除,那么对其的监控将会失效。 -// 特点: -// 1、底层使用了fsnotify机制作为异步监听插件; -// 2、(可选)文件主动自动检查作为fsnotify文件监听的辅助手段来保障监听文件如果发生改变,监控端将会及时收到提醒(解决某些业务场景下的fsnotify延迟问题); package gfsnotify import ( @@ -20,18 +17,14 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/glist" "gitee.com/johng/gf/g/container/gqueue" - "gitee.com/johng/gf/g/container/gtype" - "gitee.com/johng/gf/g/os/gtime" ) // 监听管理对象 type Watcher struct { - watcher *fsnotify.Watcher // 底层fsnotify对象 - events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 - closeChan chan struct{} // 关闭事件 - callbacks *gmap.StringInterfaceMap // 监听的回调函数 - watchUpdateTimeMap *gmap.StringIntMap // (毫秒)监控文件最新的通知时间 - activeCheckInterval *gtype.Int // (毫秒)主动文件检查时间间隔 + watcher *fsnotify.Watcher // 底层fsnotify对象 + events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 + closeChan chan struct{} // 关闭事件 + callbacks *gmap.StringInterfaceMap // 监听的回调函数 } // 监听事件对象 @@ -74,32 +67,19 @@ func Remove(path string) error { func New() (*Watcher, error) { if watch, err := fsnotify.NewWatcher(); err == nil { w := &Watcher { - watcher : watch, - events : gqueue.New(), - closeChan : make(chan struct{}, 1), - callbacks : gmap.NewStringInterfaceMap(), - watchUpdateTimeMap : gmap.NewStringIntMap(), - activeCheckInterval : gtype.NewInt(), + watcher : watch, + events : gqueue.New(), + closeChan : make(chan struct{}, 1), + callbacks : gmap.NewStringInterfaceMap(), } w.startWatchLoop() w.startEventLoop() - w.startActiveCheckLoop() return w, nil } else { return nil, err } } -// 启动主动文件更新检测机制 -func (w *Watcher) EnableActiveCheck(interval int) { - w.activeCheckInterval.Set(interval) -} - -// 关闭主动文件更新检测机制 -func (w *Watcher) DisableActiveCheck() { - w.activeCheckInterval.Set(0) -} - // 关闭监听管理对象 func (w *Watcher) Close() { w.watcher.Close() @@ -128,8 +108,6 @@ func (w *Watcher) Add(path string, callback func(event *Event)) error { }) // 添加底层监听 w.watcher.Add(path) - // 添加默认更新时间 - w.watchUpdateTimeMap.Set(path, int(gfile.MTimeMillisecond(path))) return nil } @@ -139,7 +117,7 @@ func (w *Watcher) Remove(path string) error { return w.watcher.Remove(path) } -// fsnotify监听循环 +// 监听循环 func (w *Watcher) startWatchLoop() { go func() { for { @@ -173,7 +151,6 @@ func (w *Watcher) startEventLoop() { w.watcher.Add(event.Path) continue } - w.watchUpdateTimeMap.Set(event.Path, int(gtime.Millisecond())) if l := w.callbacks.Get(event.Path); l != nil { grpool.Add(func() { for _, v := range l.(*glist.List).FrontAll() { diff --git a/g/os/gfsnotify/gfsnotify_check.go b/g/os/gfsnotify/gfsnotify_check.go deleted file mode 100644 index 7aae8acf1..000000000 --- a/g/os/gfsnotify/gfsnotify_check.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. -// -// 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://gitee.com/johng/gf. - -package gfsnotify - -import ( - "time" - "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/os/gfile" - "fmt" -) - -const ( - gDEFAULT_ACTIVE_CHECK_INTERVAL = 500 // (毫秒)默认主动检测时间间隔,未开启时什么也不做 -) - -// 文件定时监听器循环 -func (w *Watcher) startActiveCheckLoop() { - go func() { - for { - select { - // 关闭事件 - case <- w.closeChan: - return - - default: - if w.activeCheckInterval.Val() > 0 { - paths := w.watchUpdateTimeMap.Keys() - for _, path := range paths { - lastUpdateTime := w.watchUpdateTimeMap.Get(path) - if int(gtime.Millisecond()) - lastUpdateTime > w.activeCheckInterval.Val() { - - fileUpdateTime := int(gfile.MTimeMillisecond(path)) - fmt.Println("check:", path, fileUpdateTime, lastUpdateTime) - if fileUpdateTime > lastUpdateTime { - fmt.Println("update:", path) - w.watchUpdateTimeMap.Set(path, fileUpdateTime) - w.events.PushBack(&Event{ - Path : path, - Op : Op(WRITE), - }) - } - } - } - time.Sleep(time.Duration(w.activeCheckInterval.Val())*time.Millisecond) - } else { - time.Sleep(gDEFAULT_ACTIVE_CHECK_INTERVAL*time.Millisecond) - } - } - } - }() -} \ No newline at end of file diff --git a/g/os/gproc/gproc_comm.go b/g/os/gproc/gproc_comm.go index 8c1d261e7..6ff56ef34 100644 --- a/g/os/gproc/gproc_comm.go +++ b/g/os/gproc/gproc_comm.go @@ -7,28 +7,31 @@ package gproc import ( + "io" "os" "fmt" "time" + "errors" "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gfile" + "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/os/gflock" "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/g/os/gfsnotify" + "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/os/gtime" - "io" - "errors" ) const ( // 由于子进程的temp dir有可能会和父进程不一致(特别是windows下),影响进程间通信,这里统一使用环境变量设置 - gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" + gPROC_TEMP_DIR_ENV_KEY = "gproc.tempdir" // 自动通信文件清理时间间隔 - gPROC_COMM_AUTO_CLEAR_INTERVAL = time.Second + gPROC_COMM_AUTO_CLEAR_INTERVAL = time.Second // 写入通信数据失败时候的重试次数 - gPROC_COMM_FAILURE_RETRY_COUNT = 3 + gPROC_COMM_FAILURE_RETRY_COUNT = 3 + // (毫秒)主动通信内容检查时间间隔 + gPROC_COMM_ACTIVE_CHECK_INTERVAL = 500 ) // 全局通信文件清理文件锁(同一时刻只能存在一个进程进行通信文件清理) @@ -37,8 +40,8 @@ var commClearLocker = gflock.New("comm.clear.lock") var commLocker = gflock.New(fmt.Sprintf("%d.lock", os.Getpid())) // 进程通信消息队列 var commQueue = gqueue.New() -// 文件监控器 -var watcher, _ = gfsnotify.New() +// 上一次进程通信内容检查的时间 +var commLastCheckTime = gtype.NewInt64() // TCP通信数据结构定义 type Msg struct { @@ -64,6 +67,7 @@ func init() { glog.Errorfln("%s is not writable for gproc", path) os.Exit(1) } + updateLastCheckTime() if gtime.Second() - gfile.MTime(path) < 10 { // 初始化时读取已有数据(文件修改时间在10秒以内) checkCommBuffer(path) @@ -73,10 +77,9 @@ func init() { os.Truncate(path, 0) commLocker.UnLock() } - watcher.EnableActiveCheck(1000) // 文件事件监听,如果通信数据文件有任何变化,读取文件并添加到消息队列 - err := watcher.Add(path, func(event *gfsnotify.Event) { - glog.Printfln("%d: gfsnotify", Pid()) + err := gfsnotify.Add(path, func(event *gfsnotify.Event) { + updateLastCheckTime() checkCommBuffer(path) }) if err != nil { @@ -84,6 +87,12 @@ func init() { } go autoClearCommDir() + go autoActiveCheckComm() +} + +// 更新最后通信检查时间 +func updateLastCheckTime() { + commLastCheckTime.Set(gtime.Millisecond()) } // 自动清理通信目录文件 @@ -104,6 +113,17 @@ func autoClearCommDir() { } } +// 主动通信内容检测 +func autoActiveCheckComm() { + for { + time.Sleep(gPROC_COMM_ACTIVE_CHECK_INTERVAL*time.Millisecond) + if gtime.Millisecond() - commLastCheckTime.Val() > gPROC_COMM_ACTIVE_CHECK_INTERVAL { + updateLastCheckTime() + checkCommBuffer(getCommFilePath(Pid())) + } + } +} + // 手动检查进程通信消息,如果存在消息曾推送到进程消息队列 func checkCommBuffer(path string) { commLocker.Lock() @@ -146,7 +166,7 @@ func Send(pid int, data interface{}) error { } } l.UnLock() - glog.Printfln("%d to %d, %v, %d, %v", Pid(), pid, data, gfile.Size(getCommFilePath(pid)), err) + //glog.Printfln("%d to %d, %v, %d, %v", Pid(), pid, data, gfile.Size(getCommFilePath(pid)), err) return err } diff --git a/geg/other/test.go b/geg/other/test.go index 63d854de7..da3806bec 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,64 +1,20 @@ package main -import ( - "fmt" - "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/os/gtime" - "gitee.com/johng/gf/g/encoding/gbinary" - "gitee.com/johng/gf/g/os/gproc" - "os" +import "fmt" + +const ( + CREATE = 1 << iota + WRITE + REMOVE + RENAME + CHMOD ) -// 数据解包,防止黏包 -func bufferToMsgs(buffer []byte) []*gproc.Msg { - s := 0 - msgs := make([]*gproc.Msg, 0) - for s < len(buffer) { - length := gbinary.DecodeToInt(buffer[s : s + 4]) - if length < 0 || length > len(buffer) { - s++ - continue - } - checksum1 := gbinary.DecodeToUint32(buffer[s + 8 : s + 12]) - checksum2 := checksum(buffer[s + 12 : s + length]) - if checksum1 != checksum2 { - s++ - continue - } - msgs = append(msgs, &gproc.Msg { - Pid : gbinary.DecodeToInt(buffer[s + 4 : s + 8]), - Data : buffer[s + 12 : s + length], - }) - s += length - } - return msgs -} - -// 常见的二进制数据校验方式,生成校验结果 -func checksum(buffer []byte) uint32 { - var checksum uint32 - for _, b := range buffer { - checksum += uint32(b) - } - return checksum -} func main(){ - f, _ := os.Stat("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") - fmt.Println(f.ModTime().Unix()) - fmt.Println(f.ModTime().Nanosecond()) - fmt.Println(gfile.MTimeMillisecond("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go")) - return - b := gfile.GetBinContents("/home/john/Documents/11248") - for _, msg := range bufferToMsgs(b) { - fmt.Println(msg.Pid) - fmt.Println(msg.Data) - } - - return - t1 := gfile.MTime("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") - t2 := gtime.Second() - fmt.Println(t1) - fmt.Println(t2) + fmt.Println(CREATE) + fmt.Println(WRITE) + fmt.Println(REMOVE) + fmt.Println(RENAME) } \ No newline at end of file From 6c83e8b43409d4e29f1514eb845a767517f79204 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 17 May 2018 23:20:13 +0800 Subject: [PATCH 31/31] 0.98 beta --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index a4d2a08c0..7227f4322 100644 --- a/version.go +++ b/version.go @@ -1,5 +1,5 @@ package gf -const VERSION = "0.97 beta" +const VERSION = "0.98 beta" const AUTHORS = "john"