Implement plugin/template reload on SIGUSR1
There's no way around having a global mutex, because we need to update the HTTP routes when reloading plugins. During reload we need to lock the whole server. Closes: https://todo.sr.ht/~sircmpwn/koushin/43
This commit is contained in:
parent
3d8569d185
commit
ad1d2ee7f4
3 changed files with 61 additions and 40 deletions
|
@ -48,7 +48,6 @@ func main() {
|
|||
signal.Notify(sigs, syscall.SIGUSR1)
|
||||
go func() {
|
||||
for range sigs {
|
||||
e.Logger.Printf("Reloading server")
|
||||
if err := s.Reload(); err != nil {
|
||||
e.Logger.Errorf("Failed to reload server: %v", err)
|
||||
}
|
||||
|
|
89
server.go
89
server.go
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
@ -14,21 +15,23 @@ const cookieName = "koushin_session"
|
|||
|
||||
// Server holds all the koushin server state.
|
||||
type Server struct {
|
||||
renderer *renderer
|
||||
e *echo.Echo
|
||||
Sessions *SessionManager
|
||||
Plugins []Plugin
|
||||
|
||||
mutex sync.RWMutex // used for server reload
|
||||
plugins []Plugin
|
||||
|
||||
imap struct {
|
||||
host string
|
||||
tls bool
|
||||
insecure bool
|
||||
}
|
||||
|
||||
smtp struct {
|
||||
host string
|
||||
tls bool
|
||||
insecure bool
|
||||
}
|
||||
defaultTheme string
|
||||
}
|
||||
|
||||
func (s *Server) parseIMAPURL(imapURL string) error {
|
||||
|
@ -73,19 +76,53 @@ func (s *Server) parseSMTPURL(smtpURL string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Reload() error {
|
||||
return s.renderer.reload(s.Plugins)
|
||||
func (s *Server) load() error {
|
||||
plugins := append([]Plugin(nil), plugins...)
|
||||
for _, p := range plugins {
|
||||
s.e.Logger.Printf("Registered plugin '%v'", p.Name())
|
||||
}
|
||||
|
||||
func newServer(imapURL, smtpURL string) (*Server, error) {
|
||||
s := &Server{}
|
||||
luaPlugins, err := loadAllLuaPlugins(s.e.Logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load plugins: %v", err)
|
||||
}
|
||||
plugins = append(plugins, luaPlugins...)
|
||||
|
||||
if err := s.parseIMAPURL(imapURL); err != nil {
|
||||
renderer := newRenderer(s.e.Logger, s.defaultTheme)
|
||||
if err := renderer.Load(plugins); err != nil {
|
||||
return fmt.Errorf("failed to load templates: %v", err)
|
||||
}
|
||||
|
||||
// Once we've loaded plugins and templates from disk (which can take time),
|
||||
// swap them in the Server struct
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.plugins = plugins
|
||||
s.e.Renderer = renderer
|
||||
|
||||
for _, p := range plugins {
|
||||
p.SetRoutes(s.e.Group(""))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload loads Lua plugins and templates from disk.
|
||||
func (s *Server) Reload() error {
|
||||
s.e.Logger.Printf("Reloading server")
|
||||
return s.load()
|
||||
}
|
||||
|
||||
func newServer(e *echo.Echo, options *Options) (*Server, error) {
|
||||
s := &Server{e: e, defaultTheme: options.Theme}
|
||||
|
||||
if err := s.parseIMAPURL(options.IMAPURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if smtpURL != "" {
|
||||
if err := s.parseSMTPURL(smtpURL); err != nil {
|
||||
if options.SMTPURL != "" {
|
||||
if err := s.parseSMTPURL(options.SMTPURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -139,26 +176,13 @@ type Options struct {
|
|||
|
||||
// New creates a new server.
|
||||
func New(e *echo.Echo, options *Options) (*Server, error) {
|
||||
s, err := newServer(options.IMAPURL, options.SMTPURL)
|
||||
s, err := newServer(e, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.Plugins = append([]Plugin(nil), plugins...)
|
||||
for _, p := range s.Plugins {
|
||||
e.Logger.Printf("Registered plugin '%v'", p.Name())
|
||||
}
|
||||
|
||||
luaPlugins, err := loadAllLuaPlugins(e.Logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load plugins: %v", err)
|
||||
}
|
||||
s.Plugins = append(s.Plugins, luaPlugins...)
|
||||
|
||||
s.renderer = newRenderer(e.Logger, options.Theme)
|
||||
e.Renderer = s.renderer
|
||||
if err := s.renderer.reload(s.Plugins); err != nil {
|
||||
return nil, fmt.Errorf("failed to load templates: %v", err)
|
||||
if err := s.load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
|
@ -172,6 +196,15 @@ func New(e *echo.Echo, options *Options) (*Server, error) {
|
|||
c.String(code, err.Error())
|
||||
}
|
||||
|
||||
e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ectx echo.Context) error {
|
||||
s.mutex.RLock()
|
||||
err := next(ectx)
|
||||
s.mutex.RUnlock()
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ectx echo.Context) error {
|
||||
ectx.Response().Header().Set("Content-Security-Policy", "default-src 'self'")
|
||||
|
@ -211,9 +244,5 @@ func New(e *echo.Echo, options *Options) (*Server, error) {
|
|||
|
||||
e.Static("/themes", "themes")
|
||||
|
||||
for _, p := range s.Plugins {
|
||||
p.SetRoutes(e.Group(""))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
|
11
template.go
11
template.go
|
@ -6,7 +6,6 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
@ -81,19 +80,15 @@ type renderer struct {
|
|||
logger echo.Logger
|
||||
defaultTheme string
|
||||
|
||||
mutex sync.RWMutex
|
||||
base *template.Template
|
||||
themes map[string]*template.Template
|
||||
}
|
||||
|
||||
func (r *renderer) Render(w io.Writer, name string, data interface{}, ectx echo.Context) error {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
|
||||
// ectx is the raw *echo.context, not our own *Context
|
||||
ctx := ectx.Get("context").(*Context)
|
||||
|
||||
for _, plugin := range ctx.Server.Plugins {
|
||||
for _, plugin := range ctx.Server.plugins {
|
||||
if err := plugin.Inject(ctx, name, data.(RenderData)); err != nil {
|
||||
return fmt.Errorf("failed to run plugin '%v': %v", plugin.Name(), err)
|
||||
}
|
||||
|
@ -121,7 +116,7 @@ func loadTheme(name string, base *template.Template) (*template.Template, error)
|
|||
return theme, nil
|
||||
}
|
||||
|
||||
func (r *renderer) reload(plugins []Plugin) error {
|
||||
func (r *renderer) Load(plugins []Plugin) error {
|
||||
base := template.New("")
|
||||
|
||||
for _, p := range plugins {
|
||||
|
@ -155,10 +150,8 @@ func (r *renderer) reload(plugins []Plugin) error {
|
|||
}
|
||||
}
|
||||
|
||||
r.mutex.Lock()
|
||||
r.base = base
|
||||
r.themes = themes
|
||||
r.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue