From 8e3cf54d856fd5e095703054e74fdc22614c616c Mon Sep 17 00:00:00 2001 From: DarthSim Date: Thu, 27 Feb 2020 21:44:59 +0600 Subject: [PATCH] Get rid of os.Exit --- CHANGELOG.md | 3 + config.go | 130 ++++++++++++++++++++++++------------------ download.go | 16 +++++- gcs_transport.go | 7 ++- gzippool.go | 15 +++-- healthcheck.go | 8 +-- log.go | 11 +++- main.go | 77 ++++++++++++++++++++----- newrelic.go | 9 ++- processing_handler.go | 9 ++- prometheus.go | 19 ++++-- s3transport.go | 7 ++- server.go | 13 +++-- vips.go | 10 +++- 14 files changed, 225 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e51415e..82a6d7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] +### Changed +- `imgproxy -v` is replaced with `imgproxy version`. + ### Fixed - Fix loadind BMP stored in ICO. - Fix ambiguous HEIC magic bytes (MP4 videos has been detected as HEIC). diff --git a/config.go b/config.go index a6249879..348ec384 100644 --- a/config.go +++ b/config.go @@ -58,7 +58,7 @@ func boolEnvConfig(b *bool, name string) { } } -func hexEnvConfig(b *[]securityKey, name string) { +func hexEnvConfig(b *[]securityKey, name string) error { var err error if env := os.Getenv(name); len(env) > 0 { @@ -68,22 +68,24 @@ func hexEnvConfig(b *[]securityKey, name string) { for i, part := range parts { if keys[i], err = hex.DecodeString(part); err != nil { - logFatal("%s expected to be hex-encoded strings. Invalid: %s\n", name, part) + return fmt.Errorf("%s expected to be hex-encoded strings. Invalid: %s\n", name, part) } } *b = keys } + + return nil } -func hexFileConfig(b *[]securityKey, filepath string) { +func hexFileConfig(b *[]securityKey, filepath string) error { if len(filepath) == 0 { - return + return nil } f, err := os.Open(filepath) if err != nil { - logFatal("Can't open file %s\n", filepath) + return fmt.Errorf("Can't open file %s\n", filepath) } keys := []securityKey{} @@ -99,49 +101,55 @@ func hexFileConfig(b *[]securityKey, filepath string) { if key, err := hex.DecodeString(part); err == nil { keys = append(keys, key) } else { - logFatal("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part) + return fmt.Errorf("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part) } } if err := scanner.Err(); err != nil { - logFatal("Failed to read file %s: %s", filepath, err) + return fmt.Errorf("Failed to read file %s: %s", filepath, err) } *b = keys + + return nil } -func presetEnvConfig(p presets, name string) { +func presetEnvConfig(p presets, name string) error { if env := os.Getenv(name); len(env) > 0 { presetStrings := strings.Split(env, ",") for _, presetStr := range presetStrings { if err := parsePreset(p, presetStr); err != nil { - logFatal(err.Error()) + return fmt.Errorf(err.Error()) } } } + + return nil } -func presetFileConfig(p presets, filepath string) { +func presetFileConfig(p presets, filepath string) error { if len(filepath) == 0 { - return + return nil } f, err := os.Open(filepath) if err != nil { - logFatal("Can't open file %s\n", filepath) + return fmt.Errorf("Can't open file %s\n", filepath) } scanner := bufio.NewScanner(f) for scanner.Scan() { if err := parsePreset(p, scanner.Text()); err != nil { - logFatal(err.Error()) + return fmt.Errorf(err.Error()) } } if err := scanner.Err(); err != nil { - logFatal("Failed to read presets file: %s", err) + return fmt.Errorf("Failed to read presets file: %s", err) } + + return nil } type config struct { @@ -263,18 +271,12 @@ var conf = config{ BufferPoolCalibrationThreshold: 1024, } -func configure() { +func configure() error { keyPath := flag.String("keypath", "", "path of the file with hex-encoded key") saltPath := flag.String("saltpath", "", "path of the file with hex-encoded salt") presetsPath := flag.String("presets", "", "path of the file with presets") - showVersion := flag.Bool("v", false, "show version") flag.Parse() - if *showVersion { - fmt.Println(version) - os.Exit(0) - } - if port := os.Getenv("PORT"); len(port) > 0 { conf.Bind = fmt.Sprintf(":%s", port) } @@ -321,12 +323,20 @@ func configure() { boolEnvConfig(&conf.UseLinearColorspace, "IMGPROXY_USE_LINEAR_COLORSPACE") boolEnvConfig(&conf.DisableShrinkOnLoad, "IMGPROXY_DISABLE_SHRINK_ON_LOAD") - hexEnvConfig(&conf.Keys, "IMGPROXY_KEY") - hexEnvConfig(&conf.Salts, "IMGPROXY_SALT") + if err := hexEnvConfig(&conf.Keys, "IMGPROXY_KEY"); err != nil { + return err + } + if err := hexEnvConfig(&conf.Salts, "IMGPROXY_SALT"); err != nil { + return err + } intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE") - hexFileConfig(&conf.Keys, *keyPath) - hexFileConfig(&conf.Salts, *saltPath) + if err := hexFileConfig(&conf.Keys, *keyPath); err != nil { + return err + } + if err := hexFileConfig(&conf.Salts, *saltPath); err != nil { + return err + } strEnvConfig(&conf.Secret, "IMGPROXY_SECRET") @@ -350,8 +360,12 @@ func configure() { strEnvConfig(&conf.BaseURL, "IMGPROXY_BASE_URL") - presetEnvConfig(conf.Presets, "IMGPROXY_PRESETS") - presetFileConfig(conf.Presets, *presetsPath) + if err := presetEnvConfig(conf.Presets, "IMGPROXY_PRESETS"); err != nil { + return err + } + if err := presetFileConfig(conf.Presets, *presetsPath); err != nil { + return err + } boolEnvConfig(&conf.OnlyPresets, "IMGPROXY_ONLY_PRESETS") strEnvConfig(&conf.WatermarkData, "IMGPROXY_WATERMARK_DATA") @@ -379,7 +393,7 @@ func configure() { intEnvConfig(&conf.BufferPoolCalibrationThreshold, "IMGPROXY_BUFFER_POOL_CALIBRATION_THRESHOLD") if len(conf.Keys) != len(conf.Salts) { - logFatal("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts)) + return fmt.Errorf("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts)) } if len(conf.Keys) == 0 { logWarning("No keys defined, so signature checking is disabled") @@ -391,30 +405,30 @@ func configure() { } if conf.SignatureSize < 1 || conf.SignatureSize > 32 { - logFatal("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize) + return fmt.Errorf("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize) } if len(conf.Bind) == 0 { - logFatal("Bind address is not defined") + return fmt.Errorf("Bind address is not defined") } if conf.ReadTimeout <= 0 { - logFatal("Read timeout should be greater than 0, now - %d\n", conf.ReadTimeout) + return fmt.Errorf("Read timeout should be greater than 0, now - %d\n", conf.ReadTimeout) } if conf.WriteTimeout <= 0 { - logFatal("Write timeout should be greater than 0, now - %d\n", conf.WriteTimeout) + return fmt.Errorf("Write timeout should be greater than 0, now - %d\n", conf.WriteTimeout) } if conf.KeepAliveTimeout < 0 { - logFatal("KeepAlive timeout should be greater than or equal to 0, now - %d\n", conf.KeepAliveTimeout) + return fmt.Errorf("KeepAlive timeout should be greater than or equal to 0, now - %d\n", conf.KeepAliveTimeout) } if conf.DownloadTimeout <= 0 { - logFatal("Download timeout should be greater than 0, now - %d\n", conf.DownloadTimeout) + return fmt.Errorf("Download timeout should be greater than 0, now - %d\n", conf.DownloadTimeout) } if conf.Concurrency <= 0 { - logFatal("Concurrency should be greater than 0, now - %d\n", conf.Concurrency) + return fmt.Errorf("Concurrency should be greater than 0, now - %d\n", conf.Concurrency) } if conf.MaxClients <= 0 { @@ -422,43 +436,43 @@ func configure() { } if conf.TTL <= 0 { - logFatal("TTL should be greater than 0, now - %d\n", conf.TTL) + return fmt.Errorf("TTL should be greater than 0, now - %d\n", conf.TTL) } if conf.MaxSrcDimension < 0 { - logFatal("Max src dimension should be greater than or equal to 0, now - %d\n", conf.MaxSrcDimension) + return fmt.Errorf("Max src dimension should be greater than or equal to 0, now - %d\n", conf.MaxSrcDimension) } else if conf.MaxSrcDimension > 0 { logWarning("IMGPROXY_MAX_SRC_DIMENSION is deprecated and can be removed in future versions. Use IMGPROXY_MAX_SRC_RESOLUTION") } if conf.MaxSrcResolution <= 0 { - logFatal("Max src resolution should be greater than 0, now - %d\n", conf.MaxSrcResolution) + return fmt.Errorf("Max src resolution should be greater than 0, now - %d\n", conf.MaxSrcResolution) } if conf.MaxSrcFileSize < 0 { - logFatal("Max src file size should be greater than or equal to 0, now - %d\n", conf.MaxSrcFileSize) + return fmt.Errorf("Max src file size should be greater than or equal to 0, now - %d\n", conf.MaxSrcFileSize) } if conf.MaxAnimationFrames <= 0 { - logFatal("Max animation frames should be greater than 0, now - %d\n", conf.MaxAnimationFrames) + return fmt.Errorf("Max animation frames should be greater than 0, now - %d\n", conf.MaxAnimationFrames) } if conf.PngQuantizationColors < 2 { - logFatal("Png quantization colors should be greater than 1, now - %d\n", conf.PngQuantizationColors) + return fmt.Errorf("Png quantization colors should be greater than 1, now - %d\n", conf.PngQuantizationColors) } else if conf.PngQuantizationColors > 256 { - logFatal("Png quantization colors can't be greater than 256, now - %d\n", conf.PngQuantizationColors) + return fmt.Errorf("Png quantization colors can't be greater than 256, now - %d\n", conf.PngQuantizationColors) } if conf.Quality <= 0 { - logFatal("Quality should be greater than 0, now - %d\n", conf.Quality) + return fmt.Errorf("Quality should be greater than 0, now - %d\n", conf.Quality) } else if conf.Quality > 100 { - logFatal("Quality can't be greater than 100, now - %d\n", conf.Quality) + return fmt.Errorf("Quality can't be greater than 100, now - %d\n", conf.Quality) } if conf.GZipCompression < 0 { - logFatal("GZip compression should be greater than or equal to 0, now - %d\n", conf.GZipCompression) + return fmt.Errorf("GZip compression should be greater than or equal to 0, now - %d\n", conf.GZipCompression) } else if conf.GZipCompression > 9 { - logFatal("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression) + return fmt.Errorf("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression) } if conf.GZipCompression > 0 { @@ -473,11 +487,11 @@ func configure() { stat, err := os.Stat(conf.LocalFileSystemRoot) if err != nil { - logFatal("Cannot use local directory: %s", err) + return fmt.Errorf("Cannot use local directory: %s", err) } if !stat.IsDir() { - logFatal("Cannot use local directory: not a directory") + return fmt.Errorf("Cannot use local directory: not a directory") } if conf.LocalFileSystemRoot == "/" { @@ -491,32 +505,34 @@ func configure() { } if conf.WatermarkOpacity <= 0 { - logFatal("Watermark opacity should be greater than 0") + return fmt.Errorf("Watermark opacity should be greater than 0") } else if conf.WatermarkOpacity > 1 { - logFatal("Watermark opacity should be less than or equal to 1") + return fmt.Errorf("Watermark opacity should be less than or equal to 1") } if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind { - logFatal("Can't use the same binding for the main server and Prometheus") + return fmt.Errorf("Can't use the same binding for the main server and Prometheus") } if conf.FreeMemoryInterval <= 0 { - logFatal("Free memory interval should be greater than zero") + return fmt.Errorf("Free memory interval should be greater than zero") } if conf.DownloadBufferSize < 0 { - logFatal("Download buffer size should be greater than or equal to 0") + return fmt.Errorf("Download buffer size should be greater than or equal to 0") } else if conf.DownloadBufferSize > math.MaxInt32 { - logFatal("Download buffer size can't be greater than %d", math.MaxInt32) + return fmt.Errorf("Download buffer size can't be greater than %d", math.MaxInt32) } if conf.GZipBufferSize < 0 { - logFatal("GZip buffer size should be greater than or equal to 0") + return fmt.Errorf("GZip buffer size should be greater than or equal to 0") } else if conf.GZipBufferSize > math.MaxInt32 { - logFatal("GZip buffer size can't be greater than %d", math.MaxInt32) + return fmt.Errorf("GZip buffer size can't be greater than %d", math.MaxInt32) } if conf.BufferPoolCalibrationThreshold < 64 { - logFatal("Buffer pool calibration threshold should be greater than or equal to 64") + return fmt.Errorf("Buffer pool calibration threshold should be greater than or equal to 64") } + + return nil } diff --git a/download.go b/download.go index a15bd3d3..3d623a5e 100644 --- a/download.go +++ b/download.go @@ -59,7 +59,7 @@ func (lr *limitReader) Read(p []byte) (n int, err error) { return } -func initDownloading() { +func initDownloading() error { transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, MaxIdleConns: conf.Concurrency, @@ -77,11 +77,19 @@ func initDownloading() { } if conf.S3Enabled { - transport.RegisterProtocol("s3", newS3Transport()) + if t, err := newS3Transport(); err != nil { + return err + } else { + transport.RegisterProtocol("s3", t) + } } if conf.GCSEnabled { - transport.RegisterProtocol("gs", newGCSTransport()) + if t, err := newGCSTransport(); err != nil { + return err + } else { + transport.RegisterProtocol("gs", t) + } } downloadClient = &http.Client{ @@ -92,6 +100,8 @@ func initDownloading() { downloadBufPool = newBufPool("download", conf.Concurrency, conf.DownloadBufferSize) imagemeta.SetMaxSvgCheckRead(conf.MaxSvgCheckBytes) + + return nil } func checkDimensions(width, height int) error { diff --git a/gcs_transport.go b/gcs_transport.go index d0e6f3fb..e856da61 100644 --- a/gcs_transport.go +++ b/gcs_transport.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "net/http" "strconv" "strings" @@ -14,7 +15,7 @@ type gcsTransport struct { client *storage.Client } -func newGCSTransport() http.RoundTripper { +func newGCSTransport() (http.RoundTripper, error) { var ( client *storage.Client err error @@ -27,10 +28,10 @@ func newGCSTransport() http.RoundTripper { } if err != nil { - logFatal("Can't create GCS client: %s", err) + return nil, fmt.Errorf("Can't create GCS client: %s", err) } - return gcsTransport{client} + return gcsTransport{client}, nil } func (t gcsTransport) RoundTrip(req *http.Request) (*http.Response, error) { diff --git a/gzippool.go b/gzippool.go index dacc65d4..75c293be 100644 --- a/gzippool.go +++ b/gzippool.go @@ -2,6 +2,7 @@ package main import ( "compress/gzip" + "fmt" "io" "io/ioutil" "sync" @@ -17,26 +18,30 @@ type gzipPoolEntry struct { next *gzipPoolEntry } -func newGzipPool(n int) *gzipPool { +func newGzipPool(n int) (*gzipPool, error) { pool := new(gzipPool) for i := 0; i < n; i++ { - pool.grow() + if err := pool.grow(); err != nil { + return nil, err + } } - return pool + return pool, nil } -func (p *gzipPool) grow() { +func (p *gzipPool) grow() error { gz, err := gzip.NewWriterLevel(ioutil.Discard, conf.GZipCompression) if err != nil { - logFatal("Can't init GZip compression: %s", err) + return fmt.Errorf("Can't init GZip compression: %s", err) } p.top = &gzipPoolEntry{ gz: gz, next: p.top, } + + return nil } func (p *gzipPool) Get(w io.Writer) *gzip.Writer { diff --git a/healthcheck.go b/healthcheck.go index 171055fc..f185a7a4 100644 --- a/healthcheck.go +++ b/healthcheck.go @@ -9,7 +9,7 @@ import ( "os" ) -func healthcheck() { +func healthcheck() int { network := conf.Network bind := conf.Bind @@ -27,7 +27,7 @@ func healthcheck() { res, err := httpc.Get("http://imgproxy/health") if err != nil { fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return 1 } defer res.Body.Close() @@ -35,8 +35,8 @@ func healthcheck() { fmt.Fprintln(os.Stderr, string(msg)) if res.StatusCode != 200 { - os.Exit(1) + return 1 } - os.Exit(0) + return 0 } diff --git a/log.go b/log.go index 6a82b37e..e12e7a79 100644 --- a/log.go +++ b/log.go @@ -1,12 +1,13 @@ package main import ( + "fmt" "net/http" logrus "github.com/sirupsen/logrus" ) -func initLog() { +func initLog() error { logFormat := "pretty" strEnvConfig(&logFormat, "IMGPROXY_LOG_FORMAT") @@ -32,11 +33,13 @@ func initLog() { if isSyslogEnabled() { slHook, err := newSyslogHook() if err != nil { - logFatal("Unable to connect to local syslog daemon") + return fmt.Errorf("Unable to connect to syslog daemon: %s", err) } logrus.AddHook(slHook) } + + return nil } func logRequest(reqID string, r *http.Request) { @@ -96,6 +99,10 @@ func logWarning(f string, args ...interface{}) { logrus.Warnf(f, args...) } +func logError(f string, args ...interface{}) { + logrus.Errorf(f, args...) +} + func logFatal(f string, args ...interface{}) { logrus.Fatalf(f, args...) } diff --git a/main.go b/main.go index 49233cea..7bf7cbfa 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "context" + "fmt" "log" "os" "os/signal" @@ -13,29 +15,46 @@ const version = "2.10.0" type ctxKey string -func initialize() { +func initialize() error { log.SetOutput(os.Stdout) - initLog() - configure() - initNewrelic() + if err := initLog(); err != nil { + return err + } + + if err := configure(); err != nil { + return err + } + + if err := initNewrelic(); err != nil { + return err + } + initPrometheus() - initDownloading() + + if err := initDownloading(); err != nil { + return err + } + initErrorsReporting() - initVips() + + if err := initVips(); err != nil { + return err + } if err := checkPresets(conf.Presets); err != nil { shutdownVips() - logFatal(err.Error()) + return err } + + return nil } -func main() { - if len(os.Args) > 1 && os.Args[1] == "health" { - healthcheck() +func run() error { + if err := initialize(); err != nil { + return err } - initialize() defer shutdownVips() go func() { @@ -52,11 +71,43 @@ func main() { } }() - s := startServer() + ctx, cancel := context.WithCancel(context.Background()) + + if prometheusEnabled { + if err := startPrometheusServer(cancel); err != nil { + return err + } + } + + s, err := startServer(cancel) + if err != nil { + return err + } defer shutdownServer(s) stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) - <-stop + select { + case <-ctx.Done(): + case <-stop: + } + + return nil +} + +func main() { + if len(os.Args) > 1 { + switch os.Args[1] { + case "health": + os.Exit(healthcheck()) + case "version": + fmt.Println(version) + os.Exit(0) + } + } + + if err := run(); err != nil { + logFatal(err.Error()) + } } diff --git a/newrelic.go b/newrelic.go index 33c75d70..bf8ac75f 100644 --- a/newrelic.go +++ b/newrelic.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "net/http" "time" @@ -16,9 +17,9 @@ var ( newRelicTransactionCtxKey = ctxKey("newRelicTransaction") ) -func initNewrelic() { +func initNewrelic() error { if len(conf.NewRelicKey) == 0 { - return + return nil } name := conf.NewRelicAppName @@ -32,10 +33,12 @@ func initNewrelic() { newRelicApp, err = newrelic.NewApplication(config) if err != nil { - logFatal("Can't init New Relic agent: %s", err) + return fmt.Errorf("Can't init New Relic agent: %s", err) } newRelicEnabled = true + + return nil } func startNewRelicTransaction(ctx context.Context, rw http.ResponseWriter, r *http.Request) (context.Context, context.CancelFunc) { diff --git a/processing_handler.go b/processing_handler.go index d42ace7b..b14cc8bb 100644 --- a/processing_handler.go +++ b/processing_handler.go @@ -18,12 +18,15 @@ var ( headerVaryValue string ) -func initProcessingHandler() { +func initProcessingHandler() error { processingSem = make(chan struct{}, conf.Concurrency) if conf.GZipCompression > 0 { + var err error responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize) - responseGzipPool = newGzipPool(conf.Concurrency) + if responseGzipPool, err = newGzipPool(conf.Concurrency); err != nil { + return err + } } vary := make([]string, 0) @@ -41,6 +44,8 @@ func initProcessingHandler() { } headerVaryValue = strings.Join(vary, ", ") + + return nil } func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter, data []byte) { diff --git a/prometheus.go b/prometheus.go index 94e05660..dc08656a 100644 --- a/prometheus.go +++ b/prometheus.go @@ -1,6 +1,8 @@ package main import ( + "context" + "fmt" "net/http" "time" @@ -99,20 +101,25 @@ func initPrometheus() { ) prometheusEnabled = true +} +func startPrometheusServer(cancel context.CancelFunc) error { s := http.Server{Handler: promhttp.Handler()} - go func() { - l, err := listenReuseport("tcp", conf.PrometheusBind) - if err != nil { - logFatal(err.Error()) - } + l, err := listenReuseport("tcp", conf.PrometheusBind) + if err != nil { + return fmt.Errorf("Can't start Prometheus metrics server: %s", err) + } + go func() { logNotice("Starting Prometheus server at %s", conf.PrometheusBind) if err := s.Serve(l); err != nil && err != http.ErrServerClosed { - logFatal(err.Error()) + logError(err.Error()) } + cancel() }() + + return nil } func startPrometheusDuration(m prometheus.Histogram) func() { diff --git a/s3transport.go b/s3transport.go index b727950d..489d0692 100644 --- a/s3transport.go +++ b/s3transport.go @@ -1,6 +1,7 @@ package main import ( + "fmt" http "net/http" "github.com/aws/aws-sdk-go/aws" @@ -13,7 +14,7 @@ type s3Transport struct { svc *s3.S3 } -func newS3Transport() http.RoundTripper { +func newS3Transport() (http.RoundTripper, error) { s3Conf := aws.NewConfig() if len(conf.S3Region) != 0 { @@ -27,14 +28,14 @@ func newS3Transport() http.RoundTripper { sess, err := session.NewSession() if err != nil { - logFatal("Can't create S3 session: %s", err) + return nil, fmt.Errorf("Can't create S3 session: %s", err) } if sess.Config.Region == nil || len(*sess.Config.Region) == 0 { sess.Config.Region = aws.String("us-west-1") } - return s3Transport{s3.New(sess, s3Conf)} + return s3Transport{s3.New(sess, s3Conf)}, nil } func (t s3Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { diff --git a/server.go b/server.go index 18d1ab3a..12fe7d9c 100644 --- a/server.go +++ b/server.go @@ -31,10 +31,10 @@ func buildRouter() *router { return r } -func startServer() *http.Server { +func startServer(cancel context.CancelFunc) (*http.Server, error) { l, err := listenReuseport(conf.Network, conf.Bind) if err != nil { - logFatal(err.Error()) + return nil, fmt.Errorf("Can't start server: %s", err) } l = netutil.LimitListener(l, conf.MaxClients) @@ -50,16 +50,19 @@ func startServer() *http.Server { s.SetKeepAlivesEnabled(false) } - initProcessingHandler() + if err := initProcessingHandler(); err != nil { + return nil, err + } go func() { logNotice("Starting server at %s", conf.Bind) if err := s.Serve(l); err != nil && err != http.ErrServerClosed { - logFatal(err.Error()) + logError(err.Error()) } + cancel() }() - return s + return s, nil } func shutdownServer(s *http.Server) { diff --git a/vips.go b/vips.go index 47d91283..b6706c8c 100644 --- a/vips.go +++ b/vips.go @@ -9,6 +9,7 @@ package main import "C" import ( "context" + "fmt" "math" "os" "runtime" @@ -43,13 +44,13 @@ const ( vipsAngleD270 = C.VIPS_ANGLE_D270 ) -func initVips() { +func initVips() error { runtime.LockOSThread() defer runtime.UnlockOSThread() if err := C.vips_initialize(); err != 0 { C.vips_shutdown() - logFatal("unable to start vips!") + return fmt.Errorf("unable to start vips!") } // Disable libvips cache. Since processing pipeline is fine tuned, we won't get much profit from it. @@ -95,10 +96,13 @@ func initVips() { vipsConf.WatermarkOpacity = C.double(conf.WatermarkOpacity) if err := vipsLoadWatermark(); err != nil { - logFatal(err.Error()) + C.vips_shutdown() + return fmt.Errorf("Can't load watermark: %s", err) } vipsCollectMetrics() + + return nil } func shutdownVips() {