diff --git a/.gitignore b/.gitignore index 5e1899827..98b50c36e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ vendor/ /gateway /functions bolt.db +.glide/ private.sh .env diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8851556d..7d6df9b99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,24 @@ +## Building -## Building/Testing - -Build: - -```sh -# one time: +First time or when a dependency changes or when the API changes, run: +``` glide install -# then every time -./build.sh ``` -Test it, the iron token and project id are for cache. +To quick build and run (using default database): ```sh -docker run --env-file .env --rm -it --privileged -p 8080:8080 iron/functions +hack/api.sh +``` + +To build the docker image: + +```sh +hack/build.sh ``` ## Releasing ```sh -./release.sh +hack/release.sh ``` diff --git a/README.md b/README.md index 3f26e586d..d4d7331b3 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,80 @@ -Note: currently running at: http://gateway.iron.computer:8080/ - # IronFunctions -First, let's fire up an IronFunctions instance. Copy the [example.env](example.env) file into a file named `.env` and fill in the missing values. +## [Overview](/iron-io/functions/blob/master/OVERVIEW.md) -Then start your functions instance: +## Quick Start + +First let's start our IronFunctions API ``` -docker run --env-file .env --rm -it --privileged -p 8080:8080 iron/functions +docker run --rm -it -p 8080:8080 iron/functions ``` -## Usage +This command will quickly start our API using the default database `Bolt` running on `:8080` -First things first, create an app/service: -TOOD: App or service?? +Now that we have our API up and running we can quickly create our first function -### Create App +``` +curl -H "Content-Type: application/json" -X POST -d '{ + "name": "MyRoute" + "path": "/myroute" + "image": "iron/hello" +}' http://localhost:8080/v1/apps/myapp/routes +``` + +Done. Now you have our first IronFunctions route ready. + +Now let's test our new route. + +``` +curl http://localhost:8080/r/myapp/myroute +``` + +## Configuring your API + +### Databases + +These are the current databases supported by IronFunctions: + +- [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md) +- [Running with Postgres](/iron-io/functions/blob/master/docs/database/postgres.md) + +## API Usage + +### Creating applications ```sh -iron create app APP_NAME -# OR -curl -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' http://localhost:8080/api/v1/apps +curl -H "Content-Type: application/json" -X POST -d '{ + "name":"APP_NAME" +}' http://localhost:8080/v1/apps ``` -### Create a Route for your Function +### Creating routes in a application Now add routes to the app. First we'll add a route to the output of a docker container: ```sh -iron add route myapp /hello iron/hello -# OR -curl -H "Content-Type: application/json" -X POST -d '{"path":"/hello", "image":"iron/hello"}' http://localhost:8080/api/v1/apps/myapp/routes -``` - -And how about a [slackbot](https://github.com/treeder/slackbots/tree/master/guppy) too: - -```sh -curl -H "Content-Type: application/json" -X POST -d '{"path":"/guppy","image":"treeder/guppy:0.0.2", "content_type": "application/json"}' http://localhost:8080/api/v1/apps/myapp/routes +curl -H "Content-Type: application/json" -X POST -d '{ + "name": "hello", + "path":"/hello", + "image":"iron/hello" +}' http://localhost:8080/v1/apps/myapp/routes ``` ### Calling your Function -Surf to your function: http://localhost:8080/hello?app=APP_NAME . Boom! +``` +curl http://localhost:8080/r/myapp/hello +``` -#### To pass in data to your function, +### To pass in data to your function, Your function will get the body of the request as is, and the headers of the request will be passed in as env vars. ```sh -curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://localhost:8080/hello?app=APP_NAME +curl -H "Content-Type: application/json" -X POST -d '{ + "name":"Johnny" +}' http://localhost:8080/r/myapp/hello ``` ### Using IronFunctions Hosted by Iron.io @@ -56,38 +82,26 @@ curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://l Simply point to https://functions.iron.io instead of localhost and add your Iron.io Authentication header (TODO: link), like this: ```sh -curl -H "Authorization: Bearer IRON_TOKEN" -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' https://functions.iron.io/api/v1/apps +curl -H "Authorization: Bearer IRON_TOKEN" -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' https://functions.iron.io/v1/apps ``` And you'll get an ironfunctions.com host: ``` -APP_NAME.ironfunctions.com/PATH +APP_NAME.USER_ID.ironfunctions.com/PATH ``` -### Updating Your Images +## [Examples](/iron-io/functions/blob/master/examples) -Tag your images with a version, eg `treeder/guppy:0.0.5` then use that including the tag and update -the route. - -## Examples - -TODO: Link to examples in various languages -TODO: Link to slackbots (easiest way to host slackbots?) - -## Operations - -This is info on how to run and manage IronFunctions. - -### Logging - -Run logspout container on your server. - -#### Monitoring +## Logging TODO -### Scaling +## Monitoring + +TODO + +## Scaling TODO diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/database/boltdb.md b/docs/database/boltdb.md new file mode 100644 index 000000000..23c3391ed --- /dev/null +++ b/docs/database/boltdb.md @@ -0,0 +1,11 @@ +# IronFunctions using BoltDB + +BoltDB is the default database, you just need to run the API. + +## Persistent + +To keep it persistent you add a volume flag to the command: + +``` +docker run --rm -it -v $PWD/bold.db:/app/bolt.db -p 8080:8080 iron/functions +``` \ No newline at end of file diff --git a/docs/database/postgres.md b/docs/database/postgres.md new file mode 100644 index 000000000..7c232f5d5 --- /dev/null +++ b/docs/database/postgres.md @@ -0,0 +1,34 @@ +# IronFunctions using Postgres + +Let's presuppose you don't have even a postgres DB ready. + +### 1. Let's start a postgres instance: + +``` +docker run --name iron-postgres \ + -e POSTGRES_PASSWORD=ironfunctions -d postgres +``` + +### 2. Now let's create a new database to IronFunctions + +Creating database: + +``` +docker run -it --rm --link iron-postgres:postgres postgres \ + psql -h postgres -U postgres -c "CREATE DATABASE funcs;" +``` + +Granting access to postgres user + +``` +docker run -it --rm --link iron-postgres:postgres postgres \ + psql -h postgres -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE funcs TO postgres;' +``` + +### 3. Now let's start IronFunctions connecting to our new postgres instance + +``` +docker run --rm --link "iron-postgres:postgres" \ + -e "DB=postgres://postgres:ironfunctions@postgres/funcs?sslmode=disable" \ + -it -p 8080:8080 iron/functions +``` \ No newline at end of file diff --git a/example.env b/example.env deleted file mode 100644 index 4cc3f0c2f..000000000 --- a/example.env +++ /dev/null @@ -1,9 +0,0 @@ -# For IronCache -IRON_TOKEN=X -IRON_PROJECT_ID=X - -# For CloudFlare dns support -CLOUDFLARE_EMAIL=you@example.com -CLOUDFLARE_API_KEY=X -# See comments here to get zone id https://blog.cloudflare.com/cloudflare-tips-frequently-used-cloudflare-ap/#comment-2412200222 -CLOUDFLARE_ZONE_ID=y diff --git a/gateway.pem b/gateway.pem deleted file mode 100644 index 7124fbd84..000000000 --- a/gateway.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAhof/Rgb5YUGUCynxYAkbkSRQqIaaZem7g6r/lyePBk6IKzjia6QA+Ut+1KZt -uGrNXFR0THtfEWCFjIPBJfYLGmkG1gaYocWJUu/b3be3rKGlLWEuSpfHsYyLh4803QNU79Uu0ft8 -ODB4QJl54WImD1JKzAZyarDalyb+GKbnU5NAULBbTccbGFbNSwPwebvoK9G6Z8qWChPqsYAZxfyC -D2LBt0PANwB+haC6Rj0t99R6mtLRz/iKYaHz26d6UxSzNsXArJlhSCABHdQ71rbPkO0M9PvJrhfg -y+bLA4sMrHvOSjEDWGY+j1qqEYXSc/Rwe5SMd8kV7i902ks7PjcCZQIDAQABAoIBAHjvlHk9F71o -GE+Y2tV8Gn31aVS1++IVpW2NsMoO07HVsu836cLd4co5JcDAA+4+hHG1sf53AVU7sZJJdr5LWlvZ -gj2wHFGApBwcZ0f/OWxEu5n5vIVtwCRJtbyc7eaochhPShGVw2s3l0JrNXd4pcIsNfUG7qAeb8Jl -WRKMJ3OmoEMOz5M3scRypQKOulRjO6RMJCtbl4AntMYNF7cdWeuIJ3eaMD8HaYbkr1USrwGk65QC -mKdUcNl9k++Txuf7UtbRB0apFsMnAKRPUTU+9TPGwMsZSzszk8TClMNO1ALYKKY5mE+cPqrAl5gC -ZpPOf45oT+2lxktqq5u5N8XMFoECgYEA16eGE3inh23EVly5ARnowGgFtorZsI6XIJHkYsETRocf -GAvQrEMAuFWZy0n5TlNBfzPhHh9rUXWCKlbldgnDgWKpphPux3UXTqTUak+j6397rtEP85RfYqDm -QxtW0uOkKkZSGyXXEYBwTbCsQBH28VZsJEYVe+G3uXPcUu3WEZUCgYEAn7Mwgk/JP5wgYo4E3gga -fOi0/de11MV5ad337qfdUC1pf1ju9q2CyHaV3g6eo2OnynGZHYq5qlyLWoi/hTr6A+yMZSnQ15io -9ker4uyAX6DdVDmWK9uErwrqLAV+Q6HoVmxoyBbMihQW8TqX/5jZegxqipDW16+qOFxtPbZhmZEC -gYEAlD85UBFVOSggHC5Jj5Q8CGh55O62j0S2Z1Fjau/HTGh+24zjukelKxLNUo5br5hUIhmL26VF -pQ3emTR7MRWtLDii3uQ89ShtCUcOLrbovG86mwZkrNGGcMqi/+a/XOHYbKdCsh7lJcbhbMbS4oh2 -9ZivZpA3HJ4iKn6XKvsMebECgYEAhNWXU8zpqG9EwLVAdy5mWd92LG5wYDqhct2ejHQ0MayUQ8jF -e4l3bya0IbAnY+BQgKNcqKXrKTkw8G0uYLNdokXvwXW2sJ3abH/RCT+Ox/wWHSiJMJG3G6IIhfVL -wRW7G6ewwD22hGORcbU7GO8addo+BGPVUDJdc+PtOZeqNwECgYEAvLJQas3PKLL+qVmO9asvDtCl -tvOuvPFAuZBk6hLm9SSrFt5cCW+rulL8Kx1PxUk0C8LiJV8uwZIqQxE+gY5MkCfvt+xG1yurkywd -SCHOFr0m4gi8+XHvL4fmGzYPgHRi10kepSta5G8USigbcf5fOmw+Upa3qVnwot3CQdxmSfU= ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/glide.lock b/glide.lock index b8a06148c..66cbce7b7 100644 --- a/glide.lock +++ b/glide.lock @@ -1,75 +1,71 @@ -hash: a104a522dc2ba982d25101330bbd5d44778565128f0cb09f34061c167921046c -updated: 2016-07-14T08:40:37.798125603-07:00 +hash: 2101b4c83f12c75cbc3cb5be0a7d544dd3a7d21a27ef8a5ee8b014ded8cee034 +updated: 2016-07-27T16:56:51.792310167-03:00 imports: -- name: github.com/amir/raidman - version: 91c20f3f475cab75bb40ad7951d9bbdde357ade7 +- name: github.com/asaskevich/govalidator + version: 593d64559f7600f29581a3ee42177f5dbded27a9 +- name: github.com/boltdb/bolt + version: 5cc10bbbc5c141029940133bb33c9e969512a698 +- name: github.com/gin-gonic/gin + version: 4a6bc4aac4607e253bcda67c8c5bcda693d2388e subpackages: - - proto -- name: github.com/BurntSushi/toml - version: bec2dacf4b590d26237cfebff4471e21ce543494 -- name: github.com/cactus/go-statsd-client - version: 91c326c3f7bd20f0226d3d1c289dd9f8ce28d33d - subpackages: - - statsd -- name: github.com/dgrijalva/jwt-go - version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 + - binding + - render +- name: github.com/go-openapi/analysis + version: abc9a6171f5bf03ada39aead1aa7fd7bbd44d50f +- name: github.com/go-openapi/errors + version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc +- name: github.com/go-openapi/jsonpointer + version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 +- name: github.com/go-openapi/jsonreference + version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 +- name: github.com/go-openapi/loads + version: 18441dfa706d924a39a030ee2c3b1d8d81917b38 +- name: github.com/go-openapi/runtime + version: 11e322eeecc1032d5a0a96c566ed53f2b5c26e22 +- name: github.com/go-openapi/spec + version: e9fab754f5629065e6b7a6100301226545d4477e +- name: github.com/go-openapi/strfmt + version: dfda818c47a4ae5a1dde75ac776f34b2c95de993 +- name: github.com/go-openapi/swag + version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 +- name: github.com/go-openapi/validate + version: deaf2c9013bc1a7f4c774662259a506ba874d80f - name: github.com/golang/protobuf - version: 874264fbbb43f4d91e999fecb4b40143ed611400 + version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto -- name: github.com/gorilla/context - version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 -- name: github.com/gorilla/mux - version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e -- name: github.com/iron-io/common - version: 7c9faec363c808052742b4c4b84876c4c2172308 +- name: github.com/iron-io/titan + version: 697a5466b096fee73202f5ddccf8213a2357e062 + repo: git@github.com:iron-io/titan.git + vcs: git subpackages: - - httpshutdown - - msgpack - - semaphore - - serverutil -- name: github.com/iron-io/go - version: 1ed3de151aa27db91d6df5dc8c8ce164c833b57c + - jobserver/models +- name: github.com/lib/pq + version: 3cd0097429be7d611bb644ef85b42bfb102ceea4 subpackages: - - common - - common/httpshutdown - - common/msgpack - - common/semaphore - - common/stats -- name: github.com/iron-io/golog - version: 5b80d97af5a2a5d386e7609efb82192ae99a7c67 -- name: github.com/iron-io/iron_go - version: 920c950272e820d9e2562fcbf40303f05564f118 + - oid +- name: github.com/mailru/easyjson + version: 97eee20abef76a0591155412cf0d04f24b05098f subpackages: - - cache - - worker - - api - - config -- name: github.com/mattn/go-colorable - version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 -- name: github.com/mattn/go-isatty - version: 56b76bdf51f7708750eac80fa38b952bb9f32639 + - jlexer + - jwriter + - buffer +- name: github.com/manucorporat/sse + version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d +- name: github.com/PuerkitoBio/purell + version: 1d5d1cfad45d42ec5f81fa8ef23de09cebc6dcc3 +- name: github.com/PuerkitoBio/urlesc + version: 5bd2802263f21d8788851d5305584c82a5c75d7e - name: github.com/Sirupsen/logrus - version: 32055c351ea8b00b96d70f28db48d9840feaf0ec -- name: github.com/vmihailenco/bufio - version: 24e7e48f60fc2d9e99e43c07485d9fff42051e66 -- name: github.com/vrischmann/envconfig - version: 9e6e1c4d3b73427d03118518603bb904d9c55236 + version: a283a10442df8dc09befd873fab202bf8a253d6a - name: golang.org/x/net - version: a728288923b47049b2ce791836767ffbe964a5bd + version: f315505cf3349909cdf013ea56690da34e96a451 subpackages: - - proxy + - context - name: golang.org/x/sys - version: b518c298ac9dc94b6ac0757394f50d10c5dfa25a + version: a646d33e2ee3172a661fc09bca23bb4889a41bc8 subpackages: - unix -- name: gopkg.in/inconshreveable/log15.v2 - version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f - subpackages: - - stack - - term -- name: gopkg.in/mgo.v2 - version: 29cc868a5ca65f401ff318143f9408d02f4799cc - subpackages: - - bson -devImports: [] +- name: gopkg.in/go-playground/validator.v8 + version: c193cecd124b5cc722d7ee5538e945bdb3348435 +testImports: [] diff --git a/glide.yaml b/glide.yaml index 8555146a0..acbe936c9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,9 +1,14 @@ -package: github.com/iron-io/microgateway +package: github.com/iron-io/functions import: -- package: github.com/gorilla/mux -- package: github.com/iron-io/common -- package: github.com/iron-io/golog -- package: github.com/iron-io/iron_go +- package: github.com/Sirupsen/logrus +- package: github.com/boltdb/bolt +- package: github.com/gin-gonic/gin +- package: github.com/go-openapi/errors +- package: github.com/go-openapi/strfmt +- package: github.com/go-openapi/validate +- package: github.com/iron-io/titan + repo: git@github.com:iron-io/titan.git + vcs: git subpackages: - - cache - - worker + - jobserver/models +- package: github.com/lib/pq diff --git a/hack/api.sh b/hack/api.sh new file mode 100755 index 000000000..b64c7e699 --- /dev/null +++ b/hack/api.sh @@ -0,0 +1,5 @@ +set -ex + +docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' +docker build -t iron/functions:latest . +docker run --rm -it -p 8080:8080 -e LOG_LEVEL=debug -v $PWD/bolt.db:/app/bolt.db iron/functions \ No newline at end of file diff --git a/build.sh b/hack/build.sh similarity index 100% rename from build.sh rename to hack/build.sh diff --git a/release.sh b/hack/release.sh similarity index 78% rename from release.sh rename to hack/release.sh index 5cce74f80..39d344eac 100755 --- a/release.sh +++ b/hack/release.sh @@ -15,7 +15,8 @@ perl -i -pe 's/\d+\.\d+\.\K(\d+)/$1+1/e' $version_file version=$(grep -m1 -Eo "[0-9]+\.[0-9]+\.[0-9]+" $version_file) echo "Version: $version" -./build.sh +docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' +docker build -t iron/functions:latest . git add -u git commit -m "$service: $version release" diff --git a/proxy/reverser.go b/proxy/reverser.go deleted file mode 100644 index d1e3ba079..000000000 --- a/proxy/reverser.go +++ /dev/null @@ -1,192 +0,0 @@ -/* I wanted to do some stuff to this so had to make a copy. Namely: -- change the Host handling for virtual hosts. -- get errors if the proxy request fails - - -*/ - -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// HTTP reverse proxy handler - -package main - -import ( - "io" - "log" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" -) - -// onExitFlushLoop is a callback set by tests to detect the state of the -// flushLoop() goroutine. -var onExitFlushLoop func() - -// ReverseProxy is an HTTP Handler that takes an incoming request and -// sends it to another server, proxying the response back to the -// client. -type ReverseProxy struct { - // Director must be a function which modifies - // the request into a new request to be sent - // using Transport. Its response is then copied - // back to the original client unmodified. - Director func(*http.Request) - - // The transport used to perform proxy requests. - // If nil, http.DefaultTransport is used. - Transport http.RoundTripper - - // FlushInterval specifies the flush interval - // to flush to the client while copying the - // response body. - // If zero, no periodic flushing is done. - FlushInterval time.Duration -} - -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash: - return a + "/" + b - } - return a + b -} - -// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites -// URLs to the scheme, host, and base path provided in target. If the -// target's path is "/base" and the incoming request was for "/dir", -// the target request will be for /base/dir. -func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { - targetQuery := target.RawQuery - director := func(req *http.Request) { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) - if targetQuery == "" || req.URL.RawQuery == "" { - req.URL.RawQuery = targetQuery + req.URL.RawQuery - } else { - req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery - } - } - return &ReverseProxy{Director: director} -} - -func copyHeader(dst, src http.Header) { - for k, vv := range src { - for _, v := range vv { - dst.Add(k, v) - } - } -} - -func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) error { - transport := p.Transport - if transport == nil { - transport = http.DefaultTransport - } - - outreq := new(http.Request) - *outreq = *req // includes shallow copies of maps, but okay - - p.Director(outreq) - outreq.Proto = "HTTP/1.1" - outreq.ProtoMajor = 1 - outreq.ProtoMinor = 1 - outreq.Close = false - - // Remove the connection header to the backend. We want a - // persistent connection, regardless of what the client sent - // to us. This is modifying the same underlying map from req - // (shallow copied above) so we only copy it if necessary. - if outreq.Header.Get("Connection") != "" { - outreq.Header = make(http.Header) - copyHeader(outreq.Header, req.Header) - outreq.Header.Del("Connection") - } - - if clientIp, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { - outreq.Header.Set("X-Forwarded-For", clientIp) - } - - res, err := transport.RoundTrip(outreq) - if err != nil { - log.Printf("http: proxy error: %v", err) - // rw.WriteHeader(http.StatusInternalServerError) - return err - } - defer res.Body.Close() - - copyHeader(rw.Header(), res.Header) - - rw.WriteHeader(res.StatusCode) - p.copyResponse(rw, res.Body) - - return nil - -} - -func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { - if p.FlushInterval != 0 { - if wf, ok := dst.(writeFlusher); ok { - mlw := &maxLatencyWriter{ - dst: wf, - latency: p.FlushInterval, - done: make(chan bool), - } - go mlw.flushLoop() - defer mlw.stop() - dst = mlw - } - } - - io.Copy(dst, src) -} - -type writeFlusher interface { - io.Writer - http.Flusher -} - -type maxLatencyWriter struct { - dst writeFlusher - latency time.Duration - - lk sync.Mutex // protects Write + Flush - done chan bool -} - -func (m *maxLatencyWriter) Write(p []byte) (int, error) { - m.lk.Lock() - defer m.lk.Unlock() - return m.dst.Write(p) -} - -func (m *maxLatencyWriter) flushLoop() { - t := time.NewTicker(m.latency) - defer t.Stop() - for { - select { - case <-m.done: - if onExitFlushLoop != nil { - onExitFlushLoop() - } - return - case <-t.C: - m.lk.Lock() - m.dst.Flush() - m.lk.Unlock() - } - } - panic("unreached") -} - -func (m *maxLatencyWriter) stop() { m.done <- true }