
The core package uses global variables that keep from having more than one Tile38 instance runnning in the same process. Move the core variables in the server.Options type which are uniquely stated per Server instance. The build variables are still present in the core package.
489 lines
12 KiB
Go
489 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/tile38/core"
|
|
"github.com/tidwall/tile38/internal/hservice"
|
|
"github.com/tidwall/tile38/internal/log"
|
|
"github.com/tidwall/tile38/internal/server"
|
|
|
|
"golang.org/x/net/context"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
// TODO: Set to false in 2.*
|
|
var httpTransport = true
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Fire up a webhook test server by using the --webhook-http-consumer-port
|
|
// for example
|
|
// $ ./tile38-server --webhook-http-consumer-port 9999
|
|
//
|
|
// The create hooks like such...
|
|
// SETHOOK myhook http://localhost:9999/myhook NEARBY mykey FENCE POINT 33.5 -115.5 1000
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Memory profiling - start the server with the -pprofport flag
|
|
//
|
|
// $ ./tile38-server -pprofport 6060
|
|
//
|
|
// Then, at any point, from a different terminal execute:
|
|
// $ go tool pprof -svg http://localhost:6060/debug/pprof/heap > out.svg
|
|
//
|
|
// Load the SVG into a web browser to visualize the memory usage
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type hserver struct{}
|
|
|
|
func (s *hserver) Send(ctx context.Context, in *hservice.MessageRequest) (*hservice.MessageReply, error) {
|
|
return &hservice.MessageReply{Ok: true}, nil
|
|
}
|
|
|
|
func main() {
|
|
gitsha := " (" + core.GitSHA + ")"
|
|
if gitsha == " (0000000)" {
|
|
gitsha = ""
|
|
}
|
|
versionLine := `tile38-server version: ` + core.Version + gitsha
|
|
|
|
output := os.Stderr
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(output,
|
|
versionLine+`
|
|
|
|
Usage: tile38-server [-p port]
|
|
|
|
Basic Options:
|
|
-h hostname : listening host
|
|
-p port : listening port (default: 9851)
|
|
-d path : data directory (default: data)
|
|
-s socket : listen on unix socket file
|
|
-l encoding : set log encoding to json or text (default: text)
|
|
-q : no logging. totally silent output
|
|
-v : enable verbose logging
|
|
-vv : enable very verbose logging
|
|
|
|
Advanced Options:
|
|
--pidfile path : file that contains the pid
|
|
--appendonly yes/no : AOF persistence (default: yes)
|
|
--appendfilename path : AOF path (default: data/appendonly.aof)
|
|
--queuefilename path : Event queue path (default:data/queue.db)
|
|
--http-transport yes/no : HTTP transport (default: yes)
|
|
--protected-mode yes/no : protected mode (default: yes)
|
|
--nohup : do not exit on SIGHUP
|
|
|
|
Developer Options:
|
|
--dev : enable developer mode
|
|
--webhook-http-consumer-port port : Start a test HTTP webhook server
|
|
--webhook-grpc-consumer-port port : Start a test GRPC webhook server
|
|
|
|
`,
|
|
)
|
|
}
|
|
|
|
if len(os.Args) == 3 && os.Args[1] == "--webhook-http-consumer-port" {
|
|
log.SetOutput(os.Stderr)
|
|
port, err := strconv.ParseUint(os.Args[2], 10, 16)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
data, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
log.HTTPf("http: %s : %s", r.URL.Path, string(data))
|
|
})
|
|
log.Infof("webhook server http://localhost:%d/", port)
|
|
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if len(os.Args) == 3 && os.Args[1] == "--webhook-grpc-consumer-port" {
|
|
log.SetOutput(os.Stderr)
|
|
port, err := strconv.ParseUint(os.Args[2], 10, 16)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
s := grpc.NewServer()
|
|
hservice.RegisterHookServiceServer(s, &hserver{})
|
|
log.Infof("webhook server grpc://localhost:%d/", port)
|
|
if err := s.Serve(lis); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
var (
|
|
nohup bool
|
|
showEvioDisabled bool
|
|
showThreadsDisabled bool
|
|
)
|
|
|
|
var (
|
|
// use to be in core/options.go
|
|
|
|
// DevMode puts application in to dev mode
|
|
devMode = false
|
|
|
|
// ShowDebugMessages allows for log.Debug to print to console.
|
|
showDebugMessages = false
|
|
|
|
// ProtectedMode forces Tile38 to default in protected mode.
|
|
protectedMode = "no"
|
|
|
|
// AppendOnly allows for disabling the appendonly file.
|
|
appendOnly = true
|
|
|
|
// AppendFileName allows for custom appendonly file path
|
|
appendFileName = ""
|
|
|
|
// QueueFileName allows for custom queue.db file path
|
|
queueFileName = ""
|
|
)
|
|
|
|
// parse non standard args.
|
|
nargs := []string{os.Args[0]}
|
|
for i := 1; i < len(os.Args); i++ {
|
|
switch os.Args[i] {
|
|
case "--help":
|
|
output = os.Stdout
|
|
flag.Usage()
|
|
return
|
|
case "--version":
|
|
fmt.Fprintf(os.Stdout, "%s\n", versionLine)
|
|
return
|
|
case "--protected-mode", "-protected-mode":
|
|
i++
|
|
if i < len(os.Args) {
|
|
switch strings.ToLower(os.Args[i]) {
|
|
case "no":
|
|
protectedMode = "no"
|
|
continue
|
|
case "yes":
|
|
protectedMode = "yes"
|
|
continue
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
|
|
os.Exit(1)
|
|
case "--dev", "-dev":
|
|
devMode = true
|
|
continue
|
|
case "--nohup", "-nohup":
|
|
nohup = true
|
|
continue
|
|
case "--appendonly", "-appendonly":
|
|
i++
|
|
if i < len(os.Args) {
|
|
switch strings.ToLower(os.Args[i]) {
|
|
case "no":
|
|
appendOnly = false
|
|
continue
|
|
case "yes":
|
|
appendOnly = true
|
|
continue
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "appendonly must be 'yes' or 'no'\n")
|
|
os.Exit(1)
|
|
case "--appendfilename", "-appendfilename":
|
|
i++
|
|
if i == len(os.Args) || os.Args[i] == "" {
|
|
fmt.Fprintf(os.Stderr, "appendfilename must have a value\n")
|
|
os.Exit(1)
|
|
}
|
|
appendFileName = os.Args[i]
|
|
case "--queuefilename", "-queuefilename":
|
|
i++
|
|
if i == len(os.Args) || os.Args[i] == "" {
|
|
fmt.Fprintf(os.Stderr, "queuefilename must have a value\n")
|
|
os.Exit(1)
|
|
}
|
|
queueFileName = os.Args[i]
|
|
case "--http-transport", "-http-transport":
|
|
i++
|
|
if i < len(os.Args) {
|
|
switch strings.ToLower(os.Args[i]) {
|
|
case "1", "true", "yes":
|
|
httpTransport = true
|
|
continue
|
|
case "0", "false", "no":
|
|
httpTransport = false
|
|
continue
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
|
|
os.Exit(1)
|
|
case "--threads", "-threads":
|
|
i++
|
|
if i < len(os.Args) {
|
|
_, err := strconv.ParseUint(os.Args[i], 10, 16)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "threads must be a valid number\n")
|
|
os.Exit(1)
|
|
}
|
|
showThreadsDisabled = true
|
|
continue
|
|
}
|
|
fmt.Fprintf(os.Stderr, "threads must be a valid number \n")
|
|
os.Exit(1)
|
|
case "--evio", "-evio":
|
|
i++
|
|
if i < len(os.Args) {
|
|
switch strings.ToLower(os.Args[i]) {
|
|
case "no", "yes":
|
|
showEvioDisabled = true
|
|
continue
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "evio must be 'yes' or 'no'\n")
|
|
os.Exit(1)
|
|
}
|
|
nargs = append(nargs, os.Args[i])
|
|
}
|
|
os.Args = nargs
|
|
|
|
metricsAddr := flag.String("metrics-addr", "", "The listening addr for Prometheus metrics.")
|
|
|
|
var (
|
|
dir string
|
|
port int
|
|
host string
|
|
unixSocket string
|
|
verbose bool
|
|
veryVerbose bool
|
|
logEncoding string
|
|
quiet bool
|
|
pidfile string
|
|
cpuprofile string
|
|
memprofile string
|
|
pprofport int
|
|
)
|
|
|
|
flag.IntVar(&port, "p", 9851, "The listening port")
|
|
flag.StringVar(&pidfile, "pidfile", "", "A file that contains the pid")
|
|
flag.StringVar(&host, "h", "", "The listening host")
|
|
flag.StringVar(&unixSocket, "s", "", "Listen on a unix socket")
|
|
flag.StringVar(&dir, "d", "data", "The data directory")
|
|
flag.StringVar(&logEncoding, "l", "text", "The log encoding json or text (default: text)")
|
|
flag.BoolVar(&verbose, "v", false, "Enable verbose logging")
|
|
flag.BoolVar(&quiet, "q", false, "Quiet logging. Totally silent")
|
|
flag.BoolVar(&veryVerbose, "vv", false, "Enable very verbose logging")
|
|
flag.IntVar(&pprofport, "pprofport", 0, "pprofport http at port")
|
|
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`")
|
|
flag.StringVar(&memprofile, "memprofile", "", "write memory profile to `file`")
|
|
flag.Parse()
|
|
|
|
if logEncoding == "json" {
|
|
log.LogJSON = true
|
|
data, _ := os.ReadFile(filepath.Join(dir, "config"))
|
|
if gjson.GetBytes(data, "logconfig.encoding").String() == "json" {
|
|
c := gjson.GetBytes(data, "logconfig").String()
|
|
log.Build(c)
|
|
} else {
|
|
log.Build("")
|
|
}
|
|
}
|
|
|
|
var logw io.Writer = os.Stderr
|
|
if quiet {
|
|
logw = io.Discard
|
|
}
|
|
|
|
log.SetOutput(logw)
|
|
|
|
if quiet {
|
|
log.Level = 0
|
|
} else if veryVerbose {
|
|
log.Level = 3
|
|
} else if verbose {
|
|
log.Level = 2
|
|
} else {
|
|
log.Level = 1
|
|
}
|
|
|
|
showDebugMessages = veryVerbose
|
|
|
|
hostd := ""
|
|
if host != "" {
|
|
hostd = "Addr: " + host + ", "
|
|
}
|
|
|
|
// pprof
|
|
if cpuprofile != "" {
|
|
log.Debugf("cpuprofile active")
|
|
f, err := os.Create(cpuprofile)
|
|
if err != nil {
|
|
log.Fatal("could not create CPU profile: ", err)
|
|
}
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
|
log.Fatal("could not start CPU profile: ", err)
|
|
}
|
|
}
|
|
if memprofile != "" {
|
|
log.Debug("memprofile active")
|
|
}
|
|
|
|
var pprofcleanedup bool
|
|
var pprofcleanupMu sync.Mutex
|
|
pprofcleanup := func() {
|
|
pprofcleanupMu.Lock()
|
|
defer pprofcleanupMu.Unlock()
|
|
if pprofcleanedup {
|
|
return
|
|
}
|
|
// cleanup code
|
|
if cpuprofile != "" {
|
|
pprof.StopCPUProfile()
|
|
}
|
|
if memprofile != "" {
|
|
f, err := os.Create(memprofile)
|
|
if err != nil {
|
|
log.Fatal("could not create memory profile: ", err)
|
|
}
|
|
runtime.GC() // get up-to-date statistics
|
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
|
log.Fatal("could not write memory profile: ", err)
|
|
}
|
|
f.Close()
|
|
}
|
|
pprofcleanedup = true
|
|
}
|
|
defer pprofcleanup()
|
|
|
|
if pprofport != 0 {
|
|
log.Debugf("pprof http at port %d", pprofport)
|
|
go func() {
|
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", pprofport), nil))
|
|
}()
|
|
}
|
|
|
|
if unixSocket != "" {
|
|
port = 0
|
|
}
|
|
|
|
// pid file
|
|
var pidferr error
|
|
var pidcleanedup bool
|
|
var pidcleanupMu sync.Mutex
|
|
pidcleanup := func() {
|
|
if pidfile != "" {
|
|
pidcleanupMu.Lock()
|
|
defer pidcleanupMu.Unlock()
|
|
if pidcleanedup {
|
|
return
|
|
}
|
|
// cleanup code
|
|
if pidfile != "" {
|
|
os.Remove(pidfile)
|
|
}
|
|
pidcleanedup = true
|
|
}
|
|
}
|
|
defer pidcleanup()
|
|
if pidfile != "" {
|
|
os.WriteFile(pidfile, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666)
|
|
}
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
|
go func() {
|
|
for s := range c {
|
|
if s == syscall.SIGHUP && nohup {
|
|
continue
|
|
}
|
|
log.Warnf("signal: %v", s)
|
|
pidcleanup()
|
|
pprofcleanup()
|
|
switch {
|
|
default:
|
|
os.Exit(-1)
|
|
case s == syscall.SIGHUP:
|
|
os.Exit(1)
|
|
case s == syscall.SIGINT:
|
|
os.Exit(2)
|
|
case s == syscall.SIGQUIT:
|
|
os.Exit(3)
|
|
case s == syscall.SIGTERM:
|
|
os.Exit(0xf)
|
|
}
|
|
}
|
|
}()
|
|
|
|
var saddr string
|
|
if unixSocket != "" {
|
|
saddr = fmt.Sprintf("Socket: %s", unixSocket)
|
|
} else {
|
|
saddr = fmt.Sprintf("Port: %d", port)
|
|
}
|
|
|
|
if log.LogJSON {
|
|
log.Printf(`Tile38 %s%s %d bit (%s/%s) %s%s, PID: %d. Visit tile38.com/sponsor to support the project`,
|
|
core.Version, gitsha, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd, saddr, os.Getpid())
|
|
} else {
|
|
fmt.Fprintf(logw, `
|
|
_____ _ _ ___ ___
|
|
|_ _|_| |___|_ | . | Tile38 %s%s %d bit (%s/%s)
|
|
| | | | | -_|_ | . | %s%s, PID: %d
|
|
|_| |_|_|___|___|___| tile38.com
|
|
|
|
`, core.Version, gitsha, strconv.IntSize, runtime.GOARCH, runtime.GOOS, hostd,
|
|
saddr, os.Getpid())
|
|
}
|
|
|
|
if pidferr != nil {
|
|
log.Warnf("pidfile: %v", pidferr)
|
|
}
|
|
if showEvioDisabled {
|
|
log.Warnf("evio is not currently supported")
|
|
}
|
|
if showThreadsDisabled {
|
|
log.Warnf("thread flag is deprecated use GOMAXPROCS to set number of threads instead")
|
|
}
|
|
opts := server.Options{
|
|
Host: host,
|
|
Port: port,
|
|
Dir: dir,
|
|
UseHTTP: httpTransport,
|
|
MetricsAddr: *metricsAddr,
|
|
UnixSocketPath: unixSocket,
|
|
DevMode: devMode,
|
|
ShowDebugMessages: showDebugMessages,
|
|
ProtectedMode: protectedMode,
|
|
AppendOnly: appendOnly,
|
|
AppendFileName: appendFileName,
|
|
QueueFileName: queueFileName,
|
|
}
|
|
if err := server.Serve(opts); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|