summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2019-03-26 19:42:52 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2019-03-26 19:42:52 -0600
commita8dc73b4d9db5edf85e78314c9759b9d12a79b71 (patch)
treec1283aa94e2b2d605985130cdef070a24d03815e
parent86e2d1b0a48fbd84590291969611f1870471c3e0 (diff)
Performance testing Load function
-rw-r--r--admin.go37
-rw-r--r--admin_test.go30
-rw-r--r--caddy.go23
-rw-r--r--cmd/caddy2/main.go6
-rw-r--r--listeners.go19
-rw-r--r--modules/caddyhttp/caddyhttp.go21
-rw-r--r--modules/caddyhttp/caddyhttp_test.go8
7 files changed, 132 insertions, 12 deletions
diff --git a/admin.go b/admin.go
index 2841ed8..9f92db8 100644
--- a/admin.go
+++ b/admin.go
@@ -1,6 +1,7 @@
package caddy2
import (
+ "bytes"
"context"
"encoding/json"
"fmt"
@@ -8,8 +9,10 @@ import (
"log"
"net"
"net/http"
+ "net/http/pprof"
"strings"
"sync"
+ "time"
)
var (
@@ -30,6 +33,14 @@ func StartAdmin(addr string) error {
mux := http.NewServeMux()
mux.HandleFunc("/load", handleLoadConfig)
+ ///// BEGIN PPROF STUFF //////
+ mux.HandleFunc("/debug/pprof/", pprof.Index)
+ mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
+ mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
+ mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
+ mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
+ ///// END PPROF STUFF //////
+
for _, m := range GetModules("admin") {
moduleValue, err := m.New()
if err != nil {
@@ -40,7 +51,11 @@ func StartAdmin(addr string) error {
}
cfgEndptSrv = &http.Server{
- Handler: mux,
+ Handler: mux,
+ ReadTimeout: 5 * time.Second,
+ ReadHeaderTimeout: 5 * time.Second,
+ IdleTimeout: 5 * time.Second,
+ MaxHeaderBytes: 1024 * 256,
}
go cfgEndptSrv.Serve(ln)
@@ -74,6 +89,7 @@ type AdminRoute struct {
}
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
+ r.Close = true
if r.Method != "POST" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
@@ -94,14 +110,31 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
// Load loads and starts a configuration.
func Load(r io.Reader) error {
+ buf := bufPool.Get().(*bytes.Buffer)
+ buf.Reset()
+ defer bufPool.Put(buf)
+
+ _, err := io.Copy(buf, io.LimitReader(r, 1024*1024))
+ if err != nil {
+ return err
+ }
+
var cfg Config
- err := json.NewDecoder(r).Decode(&cfg)
+ err = json.Unmarshal(buf.Bytes(), &cfg)
if err != nil {
return fmt.Errorf("decoding config: %v", err)
}
+
err = Start(cfg)
if err != nil {
return fmt.Errorf("starting: %v", err)
}
+
return nil
}
+
+var bufPool = sync.Pool{
+ New: func() interface{} {
+ return new(bytes.Buffer)
+ },
+}
diff --git a/admin_test.go b/admin_test.go
new file mode 100644
index 0000000..fe2932c
--- /dev/null
+++ b/admin_test.go
@@ -0,0 +1,30 @@
+package caddy2
+
+import (
+ "strings"
+ "testing"
+)
+
+func BenchmarkLoad(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ r := strings.NewReader(`{
+ "testval": "Yippee!",
+ "modules": {
+ "http": {
+ "servers": {
+ "myserver": {
+ "listen": ["tcp/localhost:8080-8084"],
+ "read_timeout": "30s"
+ },
+ "yourserver": {
+ "listen": ["127.0.0.1:5000"],
+ "read_header_timeout": "15s"
+ }
+ }
+ }
+ }
+ }
+ `)
+ Load(r)
+ }
+}
diff --git a/caddy.go b/caddy.go
index 2f12776..40b73c5 100644
--- a/caddy.go
+++ b/caddy.go
@@ -3,10 +3,16 @@ package caddy2
import (
"encoding/json"
"fmt"
+ "log"
+ "runtime/debug"
"strings"
+ "sync"
"time"
)
+var currentCfg *Config
+var currentCfgMu sync.Mutex
+
// Start runs Caddy with the given config.
func Start(cfg Config) error {
cfg.runners = make(map[string]Runner)
@@ -26,16 +32,33 @@ func Start(cfg Config) error {
for name, r := range cfg.runners {
err := r.Run()
if err != nil {
+ // TODO: If any one has an error, stop the others
return fmt.Errorf("%s module: %v", name, err)
}
}
+ currentCfgMu.Lock()
+ if currentCfg != nil {
+ for _, r := range cfg.runners {
+ err := r.Cancel()
+ if err != nil {
+ log.Println(err)
+ }
+ }
+ }
+ currentCfg = &cfg
+ currentCfgMu.Unlock()
+
+ // TODO: debugging memory leak...
+ debug.FreeOSMemory()
+
return nil
}
// Runner is a thing that Caddy runs.
type Runner interface {
Run() error
+ Cancel() error
}
// Config represents a Caddy configuration.
diff --git a/cmd/caddy2/main.go b/cmd/caddy2/main.go
index 9e482c1..234a88d 100644
--- a/cmd/caddy2/main.go
+++ b/cmd/caddy2/main.go
@@ -5,17 +5,19 @@ import (
"bitbucket.org/lightcodelabs/caddy2"
+ _ "net/http/pprof"
+
// this is where modules get plugged in
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
_ "bitbucket.org/lightcodelabs/dynamicconfig"
)
func main() {
- err := caddy2.Start("127.0.0.1:1234")
+ err := caddy2.StartAdmin("127.0.0.1:1234")
if err != nil {
log.Fatal(err)
}
- defer caddy2.Stop()
+ defer caddy2.StopAdmin()
select {}
}
diff --git a/listeners.go b/listeners.go
index 962cb1d..0a2fe1c 100644
--- a/listeners.go
+++ b/listeners.go
@@ -3,15 +3,29 @@ package caddy2
import (
"fmt"
"net"
+ "sync"
"sync/atomic"
)
// Listen returns a listener suitable for use in a Caddy module.
func Listen(proto, addr string) (net.Listener, error) {
+ lnKey := proto + "/" + addr
+
+ listenersMu.Lock()
+ defer listenersMu.Unlock()
+
+ // if listener already exists, return it
+ if ln, ok := listeners[lnKey]; ok {
+ return &fakeCloseListener{Listener: ln}, nil
+ }
+
+ // or, create new one and save it
ln, err := net.Listen(proto, addr)
if err != nil {
return nil, err
}
+ listeners[lnKey] = ln
+
return &fakeCloseListener{Listener: ln}, nil
}
@@ -49,3 +63,8 @@ func (fcl *fakeCloseListener) CloseUnderlying() error {
// Close() is called, indicating that it is pretending to
// be closed so that the server using it can terminate.
var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉")
+
+var (
+ listeners = make(map[string]net.Listener)
+ listenersMu sync.Mutex
+)
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go
index 99efef0..ec2637a 100644
--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -1,6 +1,7 @@
package caddyhttp
import (
+ "context"
"fmt"
"log"
"net"
@@ -24,13 +25,15 @@ func init() {
type httpModuleConfig struct {
Servers map[string]httpServerConfig `json:"servers"`
+
+ servers []*http.Server
}
func (hc *httpModuleConfig) Run() error {
- fmt.Printf("RUNNING: %#v\n", hc)
+ // fmt.Printf("RUNNING: %#v\n", hc)
for _, srv := range hc.Servers {
- s := http.Server{
+ s := &http.Server{
ReadTimeout: time.Duration(srv.ReadTimeout),
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
}
@@ -53,11 +56,21 @@ func (hc *httpModuleConfig) Run() error {
return nil
}
+func (hc *httpModuleConfig) Cancel() error {
+ for _, s := range hc.servers {
+ err := s.Shutdown(context.Background()) // TODO
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func parseListenAddr(a string) (proto string, addrs []string, err error) {
proto = "tcp"
- if idx := strings.Index(a, ":::"); idx >= 0 {
+ if idx := strings.Index(a, "/"); idx >= 0 {
proto = strings.ToLower(strings.TrimSpace(a[:idx]))
- a = a[idx+3:]
+ a = a[idx+1:]
}
var host, port string
host, port, err = net.SplitHostPort(a)
diff --git a/modules/caddyhttp/caddyhttp_test.go b/modules/caddyhttp/caddyhttp_test.go
index c65a9a2..610a5f0 100644
--- a/modules/caddyhttp/caddyhttp_test.go
+++ b/modules/caddyhttp/caddyhttp_test.go
@@ -28,22 +28,22 @@ func TestParseListenerAddr(t *testing.T) {
expectAddrs: []string{":1234"},
},
{
- input: "tcp::::1234",
+ input: "tcp/:1234",
expectProto: "tcp",
expectAddrs: []string{":1234"},
},
{
- input: "tcp6::::1234",
+ input: "tcp6/:1234",
expectProto: "tcp6",
expectAddrs: []string{":1234"},
},
{
- input: "tcp4:::localhost:1234",
+ input: "tcp4/localhost:1234",
expectProto: "tcp4",
expectAddrs: []string{"localhost:1234"},
},
{
- input: "unix:::localhost:1234-1236",
+ input: "unix/localhost:1234-1236",
expectProto: "unix",
expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"},
},