diff --git a/api/runner/task.go b/api/runner/task.go index e8619c997..03c0ee098 100644 --- a/api/runner/task.go +++ b/api/runner/task.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/fnproject/fn/api/runner/drivers" + "github.com/fnproject/fn/api/runner/protocol" "github.com/fnproject/fn/api/runner/task" docker "github.com/fsouza/go-dockerclient" ) @@ -62,6 +63,9 @@ type containerTask struct { func (t *containerTask) Command() string { return "" } func (t *containerTask) EnvVars() map[string]string { + if protocol.IsStreamable(protocol.Protocol(t.cfg.Format)) { + return t.cfg.BaseEnv + } return t.cfg.Env } func (t *containerTask) Input() io.Reader { @@ -82,9 +86,8 @@ func (t *containerTask) IdleTimeout() time.Duration { return t.cfg.IdleTimeo func (t *containerTask) Logger() (io.Writer, io.Writer) { return t.cfg.Stdout, t.cfg.Stderr } func (t *containerTask) Volumes() [][2]string { return [][2]string{} } func (t *containerTask) WorkDir() string { return "" } - -func (t *containerTask) Close() {} -func (t *containerTask) WriteStat(drivers.Stat) {} +func (t *containerTask) Close() {} +func (t *containerTask) WriteStat(drivers.Stat) {} // Implementing the docker.AuthConfiguration interface. Pulling in // the docker repo password from environment variables diff --git a/api/runner/task/task.go b/api/runner/task/task.go index 219c586a2..cc568ca38 100644 --- a/api/runner/task/task.go +++ b/api/runner/task/task.go @@ -16,7 +16,8 @@ type Config struct { IdleTimeout time.Duration AppName string Memory uint64 - Env map[string]string + BaseEnv map[string]string // only app & route config vals [for hot] + Env map[string]string // includes BaseEnv Format string ReceivedTime time.Time // Ready is used to await the first pull diff --git a/api/runner/worker.go b/api/runner/worker.go index 70cfe5f24..ec2935125 100644 --- a/api/runner/worker.go +++ b/api/runner/worker.go @@ -2,6 +2,7 @@ package runner import ( "context" + "crypto/sha1" "errors" "fmt" "io" @@ -132,8 +133,8 @@ func (h *htfnmgr) getPipe(ctx context.Context, rnr *Runner, cfg *task.Config) ch h.RLock() } - // TODO(ccirello): re-implement this without memory allocation (fmt.Sprint) - fn := fmt.Sprint(cfg.AppName, ",", cfg.Path, cfg.Image, cfg.Timeout, cfg.Memory, cfg.Format) + fn := key(cfg) + svr, ok := h.hc[fn] h.RUnlock() if !ok { @@ -149,6 +150,28 @@ func (h *htfnmgr) getPipe(ctx context.Context, rnr *Runner, cfg *task.Config) ch return svr.tasksin } +func key(cfg *task.Config) string { + // TODO we should probably colocate this with Config, but it's kind of hot + // specific so it makes sense here, too (just brittle & hidden) + + // return a sha1 hash of a (hopefully) unique string of all the config + // values, to make map lookups quicker [than the giant unique string] + hash := sha1.New() + fmt.Fprint(hash, cfg.AppName, "\x00") + fmt.Fprint(hash, cfg.Path, "\x00") + fmt.Fprint(hash, cfg.Image, "\x00") + for k, v := range cfg.BaseEnv { + fmt.Fprint(hash, k, "\x00", v, "\x00") + } + fmt.Fprint(hash, cfg.Timeout, "\x00") + fmt.Fprint(hash, cfg.IdleTimeout, "\x00") + fmt.Fprint(hash, cfg.Memory, "\x00") + fmt.Fprint(hash, cfg.Format, "\x00") + + var buf [sha1.Size]byte + return string(hash.Sum(buf[:])) +} + // htfnsvr is part of htfnmgr, abstracted apart for simplicity, its only // purpose is to test for hot functions saturation and try starting as many as // needed. In case of absence of workload, it will stop trying to start new hot @@ -350,7 +373,6 @@ func (hc *htfn) serve(ctx context.Context) { } }() - cfg.Env["FN_FORMAT"] = cfg.Format cfg.Timeout = 0 // add a timeout to simulate ab.end. failure. cfg.Stdin = hc.containerIn cfg.Stdout = hc.containerOut diff --git a/api/server/runner.go b/api/server/runner.go index 46ce41eab..dae350af0 100644 --- a/api/server/runner.go +++ b/api/server/runner.go @@ -142,28 +142,39 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, rout if route.Format == "" { route.Format = "default" } - envVars := map[string]string{ - "METHOD": c.Request.Method, - "APP_NAME": appName, - "ROUTE": route.Path, - "REQUEST_URL": fmt.Sprintf("%v//%v%v", func() string { - if c.Request.TLS == nil { - return "http" - } - return "https" - }(), c.Request.Host, c.Request.URL.String()), - "CALL_ID": reqID, - "FORMAT": route.Format, - } + + // baseVars are the vars on the route & app, not on this specific request [for hot functions] + baseVars := make(map[string]string, len(app.Config)+len(route.Config)+3) + baseVars["FN_FORMAT"] = route.Format + baseVars["APP_NAME"] = appName + baseVars["ROUTE"] = route.Path // app config for k, v := range app.Config { - envVars[toEnvName("", k)] = v + k = toEnvName("", k) + baseVars[k] = v } for k, v := range route.Config { - envVars[toEnvName("", k)] = v + k = toEnvName("", k) + baseVars[k] = v } + // envVars contains the full set of env vars, per request + base + envVars := make(map[string]string, len(baseVars)+len(params)+len(c.Request.Header)+3) + + for k, v := range baseVars { + envVars[k] = v + } + + envVars["CALL_ID"] = reqID + envVars["METHOD"] = c.Request.Method + envVars["REQUEST_URL"] = fmt.Sprintf("%v//%v%v", func() string { + if c.Request.TLS == nil { + return "http" + } + return "https" + }(), c.Request.Host, c.Request.URL.String()) + // params for _, param := range params { envVars[toEnvName("PARAM", param.Key)] = param.Value @@ -177,6 +188,7 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, rout cfg := &task.Config{ AppName: appName, Path: route.Path, + BaseEnv: baseVars, Env: envVars, Format: route.Format, ID: reqID,