pax_global_header00006660000000000000000000000064134504003610014506gustar00rootroot0000000000000052 comment=92f87ca46e0399b52678e97de9d4218ad03a3e61 golang-github-jbenet-go-context-0.0~git20150711.d14ea06/000077500000000000000000000000001345040036100222145ustar00rootroot00000000000000golang-github-jbenet-go-context-0.0~git20150711.d14ea06/LICENSE000066400000000000000000000020731345040036100232230ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Juan Batiz-Benet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-jbenet-go-context-0.0~git20150711.d14ea06/README.md000066400000000000000000000005341345040036100234750ustar00rootroot00000000000000### go-context - jbenet's CONText EXTensions https://godoc.org/github.com/jbenet/go-context - `WithDeadlineFraction`: https://godoc.org/github.com/jbenet/go-context/ext#WithDeadlineFraction - `WithParents`: https://godoc.org/github.com/jbenet/go-context/ext#WithParents - `io.{Reader, Writer}`: https://godoc.org/github.com/jbenet/go-context/io golang-github-jbenet-go-context-0.0~git20150711.d14ea06/dag/000077500000000000000000000000001345040036100227475ustar00rootroot00000000000000golang-github-jbenet-go-context-0.0~git20150711.d14ea06/dag/dagctx.go000066400000000000000000000047401345040036100245550ustar00rootroot00000000000000package ctxext import ( "sync" "time" context "golang.org/x/net/context" ) // WithParents returns a Context that listens to all given // parents. It effectively transforms the Context Tree into // a Directed Acyclic Graph. This is useful when a context // may be cancelled for more than one reason. For example, // consider a database with the following Get function: // // func (db *DB) Get(ctx context.Context, ...) {} // // DB.Get may have to stop for two different contexts: // * the caller's context (caller might cancel) // * the database's context (might be shut down mid-request) // // WithParents saves the day by allowing us to "merge" contexts // and continue on our merry contextual way: // // ctx = ctxext.WithParents(ctx, db.ctx) // // Passing related (mutually derived) contexts to WithParents is // actually ok. The child is cancelled when any of its parents is // cancelled, so if any of its parents are also related, the cancel // propagation will reach the child via the shortest path. func WithParents(ctxts ...context.Context) context.Context { if len(ctxts) < 1 { panic("no contexts provided") } ctx := &errCtx{ done: make(chan struct{}), dead: earliestDeadline(ctxts), } // listen to all contexts and use the first. for _, c2 := range ctxts { go func(pctx context.Context) { select { case <-ctx.Done(): // cancelled by another parent return case <-pctx.Done(): // this parent cancelled // race: two parents may have cancelled at the same time. // break tie with mutex (inside c.cancel) ctx.cancel(pctx.Err()) } }(c2) } return ctx } func earliestDeadline(ctxts []context.Context) *time.Time { var d1 *time.Time for _, c := range ctxts { if c == nil { panic("given nil context.Context") } // use earliest deadline. d2, ok := c.Deadline() if !ok { continue } if d1 == nil || (*d1).After(d2) { d1 = &d2 } } return d1 } type errCtx struct { dead *time.Time done chan struct{} err error mu sync.RWMutex } func (c *errCtx) cancel(err error) { c.mu.Lock() defer c.mu.Unlock() select { case <-c.Done(): return default: } c.err = err close(c.done) // signal done to all } func (c *errCtx) Done() <-chan struct{} { return c.done } func (c *errCtx) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err } func (c *errCtx) Value(key interface{}) interface{} { return nil } func (c *errCtx) Deadline() (deadline time.Time, ok bool) { if c.dead == nil { return } return *c.dead, true } golang-github-jbenet-go-context-0.0~git20150711.d14ea06/dag/dagctx_test.go000066400000000000000000000053151345040036100256130ustar00rootroot00000000000000package ctxext import ( "math/rand" "testing" "time" context "golang.org/x/net/context" ) func TestWithParentsSingle(t *testing.T) { ctx1, cancel := context.WithCancel(context.Background()) ctx2 := WithParents(ctx1) select { case <-ctx2.Done(): t.Fatal("ended too early") case <-time.After(time.Millisecond): } cancel() select { case <-ctx2.Done(): case <-time.After(time.Millisecond): t.Error("should've cancelled it") } if ctx2.Err() != ctx1.Err() { t.Error("errors should match") } } func TestWithParentsDeadline(t *testing.T) { ctx1, _ := context.WithCancel(context.Background()) ctx2, _ := context.WithTimeout(context.Background(), time.Second) ctx3, _ := context.WithTimeout(context.Background(), time.Second*2) ctx := WithParents(ctx1) d, ok := ctx.Deadline() if ok { t.Error("ctx should have no deadline") } ctx = WithParents(ctx1, ctx2, ctx3) d, ok = ctx.Deadline() d2, ok2 := ctx2.Deadline() if !ok { t.Error("ctx should have deadline") } else if !ok2 { t.Error("ctx2 should have deadline") } else if !d.Equal(d2) { t.Error("ctx should have ctx2 deadline") } } func SubtestWithParentsMany(t *testing.T, n int) { ctxs := make([]context.Context, n) cancels := make([]context.CancelFunc, n) for i := 0; i < n; i++ { if i == 0 { // first must be new. ctxs[i], cancels[i] = context.WithCancel(context.Background()) continue } r := rand.Intn(i) // select a previous context switch rand.Intn(6) { case 0: // same as old ctxs[i], cancels[i] = ctxs[r], cancels[r] case 1: // derive from another ctxs[i], cancels[i] = context.WithCancel(ctxs[r]) case 2: // deadline t := (time.Second) * time.Duration(r+2) // +2 so we dont run into 0 or timing bugs ctxs[i], cancels[i] = context.WithTimeout(ctxs[r], t) default: // new context ctxs[i], cancels[i] = context.WithCancel(context.Background()) } } ctx := WithParents(ctxs...) // test deadline is earliest. d1 := earliestDeadline(ctxs) d2, ok := ctx.Deadline() switch { case d1 == nil && ok: t.Error("nil, should not have deadline") case d1 != nil && !ok: t.Error("not nil, should have deadline") case d1 != nil && ok && !d1.Equal(d2): t.Error("should find same deadline") } if ok { t.Logf("deadline - now: %s", d2.Sub(time.Now())) } select { case <-ctx.Done(): t.Fatal("ended too early") case <-time.After(time.Millisecond): } // cancel just one r := rand.Intn(len(cancels)) cancels[r]() select { case <-ctx.Done(): case <-time.After(time.Millisecond): t.Error("any should've cancelled it") } if ctx.Err() != ctxs[r].Err() { t.Error("errors should match") } } func TestWithParentsMany(t *testing.T) { n := 100 for i := 1; i < n; i++ { SubtestWithParentsMany(t, i) } } golang-github-jbenet-go-context-0.0~git20150711.d14ea06/doc.go000066400000000000000000000001321345040036100233040ustar00rootroot00000000000000// Package context contains some extenstions to go.net/context by @jbenet package context golang-github-jbenet-go-context-0.0~git20150711.d14ea06/frac/000077500000000000000000000000001345040036100231275ustar00rootroot00000000000000golang-github-jbenet-go-context-0.0~git20150711.d14ea06/frac/fracctx.go000066400000000000000000000036741345040036100251220ustar00rootroot00000000000000// Package ctxext provides multiple useful context constructors. package ctxext import ( "time" context "golang.org/x/net/context" ) // WithDeadlineFraction returns a Context with a fraction of the // original context's timeout. This is useful in sequential pipelines // of work, where one might try options and fall back to others // depending on the time available, or failure to respond. For example: // // // getPicture returns a picture from our encrypted database // // we have a pipeline of multiple steps. we need to: // // - get the data from a database // // - decrypt it // // - apply many transforms // // // // we **know** that each step takes increasingly more time. // // The transforms are much more expensive than decryption, and // // decryption is more expensive than the database lookup. // // If our database takes too long (i.e. >0.2 of available time), // // there's no use in continuing. // func getPicture(ctx context.Context, key string) ([]byte, error) { // // fractional timeout contexts to the rescue! // // // try the database with 0.2 of remaining time. // ctx1, _ := ctxext.WithDeadlineFraction(ctx, 0.2) // val, err := db.Get(ctx1, key) // if err != nil { // return nil, err // } // // // try decryption with 0.3 of remaining time. // ctx2, _ := ctxext.WithDeadlineFraction(ctx, 0.3) // if val, err = decryptor.Decrypt(ctx2, val); err != nil { // return nil, err // } // // // try transforms with all remaining time. hopefully it's enough! // return transformer.Transform(ctx, val) // } // // func WithDeadlineFraction(ctx context.Context, fraction float64) ( context.Context, context.CancelFunc) { d, found := ctx.Deadline() if !found { // no deadline return context.WithCancel(ctx) } left := d.Sub(time.Now()) if left < 0 { // already passed... return context.WithCancel(ctx) } left = time.Duration(float64(left) * fraction) return context.WithTimeout(ctx, left) } golang-github-jbenet-go-context-0.0~git20150711.d14ea06/frac/fracctx_test.go000066400000000000000000000050471345040036100261550ustar00rootroot00000000000000package ctxext import ( "os" "testing" "time" context "golang.org/x/net/context" ) // this test is on the context tool itself, not our stuff. it's for sanity on ours. func TestDeadline(t *testing.T) { if os.Getenv("TRAVIS") == "true" { t.Skip("timeouts don't work reliably on travis") } ctx, _ := context.WithTimeout(context.Background(), 5*time.Millisecond) select { case <-ctx.Done(): t.Fatal("ended too early") default: } <-time.After(6 * time.Millisecond) select { case <-ctx.Done(): default: t.Fatal("ended too late") } } func TestDeadlineFractionForever(t *testing.T) { ctx, _ := WithDeadlineFraction(context.Background(), 0.5) _, found := ctx.Deadline() if found { t.Fatal("should last forever") } } func TestDeadlineFractionHalf(t *testing.T) { if os.Getenv("TRAVIS") == "true" { t.Skip("timeouts don't work reliably on travis") } ctx1, _ := context.WithTimeout(context.Background(), 10*time.Millisecond) ctx2, _ := WithDeadlineFraction(ctx1, 0.5) select { case <-ctx1.Done(): t.Fatal("ctx1 ended too early") case <-ctx2.Done(): t.Fatal("ctx2 ended too early") default: } <-time.After(2 * time.Millisecond) select { case <-ctx1.Done(): t.Fatal("ctx1 ended too early") case <-ctx2.Done(): t.Fatal("ctx2 ended too early") default: } <-time.After(4 * time.Millisecond) select { case <-ctx1.Done(): t.Fatal("ctx1 ended too early") case <-ctx2.Done(): default: t.Fatal("ctx2 ended too late") } <-time.After(6 * time.Millisecond) select { case <-ctx1.Done(): default: t.Fatal("ctx1 ended too late") } } func TestDeadlineFractionCancel(t *testing.T) { ctx1, cancel1 := context.WithTimeout(context.Background(), 10*time.Millisecond) ctx2, cancel2 := WithDeadlineFraction(ctx1, 0.5) select { case <-ctx1.Done(): t.Fatal("ctx1 ended too early") case <-ctx2.Done(): t.Fatal("ctx2 ended too early") default: } cancel2() select { case <-ctx1.Done(): t.Fatal("ctx1 should NOT be cancelled") case <-ctx2.Done(): default: t.Fatal("ctx2 should be cancelled") } cancel1() select { case <-ctx1.Done(): case <-ctx2.Done(): default: t.Fatal("ctx1 should be cancelled") } } func TestDeadlineFractionObeysParent(t *testing.T) { ctx1, cancel1 := context.WithTimeout(context.Background(), 10*time.Millisecond) ctx2, _ := WithDeadlineFraction(ctx1, 0.5) select { case <-ctx1.Done(): t.Fatal("ctx1 ended too early") case <-ctx2.Done(): t.Fatal("ctx2 ended too early") default: } cancel1() select { case <-ctx2.Done(): default: t.Fatal("ctx2 should be cancelled") } } golang-github-jbenet-go-context-0.0~git20150711.d14ea06/io/000077500000000000000000000000001345040036100226235ustar00rootroot00000000000000golang-github-jbenet-go-context-0.0~git20150711.d14ea06/io/ctxio.go000066400000000000000000000061661345040036100243110ustar00rootroot00000000000000// Package ctxio provides io.Reader and io.Writer wrappers that // respect context.Contexts. Use these at the interface between // your context code and your io. // // WARNING: read the code. see how writes and reads will continue // until you cancel the io. Maybe this package should provide // versions of io.ReadCloser and io.WriteCloser that automatically // call .Close when the context expires. But for now -- since in my // use cases I have long-lived connections with ephemeral io wrappers // -- this has yet to be a need. package ctxio import ( "io" context "golang.org/x/net/context" ) type ioret struct { n int err error } type Writer interface { io.Writer } type ctxWriter struct { w io.Writer ctx context.Context } // NewWriter wraps a writer to make it respect given Context. // If there is a blocking write, the returned Writer will return // whenever the context is cancelled (the return values are n=0 // and err=ctx.Err().) // // Note well: this wrapper DOES NOT ACTUALLY cancel the underlying // write-- there is no way to do that with the standard go io // interface. So the read and write _will_ happen or hang. So, use // this sparingly, make sure to cancel the read or write as necesary // (e.g. closing a connection whose context is up, etc.) // // Furthermore, in order to protect your memory from being read // _after_ you've cancelled the context, this io.Writer will // first make a **copy** of the buffer. func NewWriter(ctx context.Context, w io.Writer) *ctxWriter { if ctx == nil { ctx = context.Background() } return &ctxWriter{ctx: ctx, w: w} } func (w *ctxWriter) Write(buf []byte) (int, error) { buf2 := make([]byte, len(buf)) copy(buf2, buf) c := make(chan ioret, 1) go func() { n, err := w.w.Write(buf2) c <- ioret{n, err} close(c) }() select { case r := <-c: return r.n, r.err case <-w.ctx.Done(): return 0, w.ctx.Err() } } type Reader interface { io.Reader } type ctxReader struct { r io.Reader ctx context.Context } // NewReader wraps a reader to make it respect given Context. // If there is a blocking read, the returned Reader will return // whenever the context is cancelled (the return values are n=0 // and err=ctx.Err().) // // Note well: this wrapper DOES NOT ACTUALLY cancel the underlying // write-- there is no way to do that with the standard go io // interface. So the read and write _will_ happen or hang. So, use // this sparingly, make sure to cancel the read or write as necesary // (e.g. closing a connection whose context is up, etc.) // // Furthermore, in order to protect your memory from being read // _before_ you've cancelled the context, this io.Reader will // allocate a buffer of the same size, and **copy** into the client's // if the read succeeds in time. func NewReader(ctx context.Context, r io.Reader) *ctxReader { return &ctxReader{ctx: ctx, r: r} } func (r *ctxReader) Read(buf []byte) (int, error) { buf2 := make([]byte, len(buf)) c := make(chan ioret, 1) go func() { n, err := r.r.Read(buf2) c <- ioret{n, err} close(c) }() select { case ret := <-c: copy(buf, buf2) return ret.n, ret.err case <-r.ctx.Done(): return 0, r.ctx.Err() } } golang-github-jbenet-go-context-0.0~git20150711.d14ea06/io/ctxio_test.go000066400000000000000000000121611345040036100253400ustar00rootroot00000000000000package ctxio import ( "bytes" "io" "testing" "time" context "golang.org/x/net/context" ) func TestReader(t *testing.T) { buf := []byte("abcdef") buf2 := make([]byte, 3) r := NewReader(context.Background(), bytes.NewReader(buf)) // read first half n, err := r.Read(buf2) if n != 3 { t.Error("n should be 3") } if err != nil { t.Error("should have no error") } if string(buf2) != string(buf[:3]) { t.Error("incorrect contents") } // read second half n, err = r.Read(buf2) if n != 3 { t.Error("n should be 3") } if err != nil { t.Error("should have no error") } if string(buf2) != string(buf[3:6]) { t.Error("incorrect contents") } // read more. n, err = r.Read(buf2) if n != 0 { t.Error("n should be 0", n) } if err != io.EOF { t.Error("should be EOF", err) } } func TestWriter(t *testing.T) { var buf bytes.Buffer w := NewWriter(context.Background(), &buf) // write three n, err := w.Write([]byte("abc")) if n != 3 { t.Error("n should be 3") } if err != nil { t.Error("should have no error") } if string(buf.Bytes()) != string("abc") { t.Error("incorrect contents") } // write three more n, err = w.Write([]byte("def")) if n != 3 { t.Error("n should be 3") } if err != nil { t.Error("should have no error") } if string(buf.Bytes()) != string("abcdef") { t.Error("incorrect contents") } } func TestReaderCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) piper, pipew := io.Pipe() r := NewReader(ctx, piper) buf := make([]byte, 10) done := make(chan ioret) go func() { n, err := r.Read(buf) done <- ioret{n, err} }() pipew.Write([]byte("abcdefghij")) select { case ret := <-done: if ret.n != 10 { t.Error("ret.n should be 10", ret.n) } if ret.err != nil { t.Error("ret.err should be nil", ret.err) } if string(buf) != "abcdefghij" { t.Error("read contents differ") } case <-time.After(20 * time.Millisecond): t.Fatal("failed to read") } go func() { n, err := r.Read(buf) done <- ioret{n, err} }() cancel() select { case ret := <-done: if ret.n != 0 { t.Error("ret.n should be 0", ret.n) } if ret.err == nil { t.Error("ret.err should be ctx error", ret.err) } case <-time.After(20 * time.Millisecond): t.Fatal("failed to stop reading after cancel") } } func TestWriterCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) piper, pipew := io.Pipe() w := NewWriter(ctx, pipew) buf := make([]byte, 10) done := make(chan ioret) go func() { n, err := w.Write([]byte("abcdefghij")) done <- ioret{n, err} }() piper.Read(buf) select { case ret := <-done: if ret.n != 10 { t.Error("ret.n should be 10", ret.n) } if ret.err != nil { t.Error("ret.err should be nil", ret.err) } if string(buf) != "abcdefghij" { t.Error("write contents differ") } case <-time.After(20 * time.Millisecond): t.Fatal("failed to write") } go func() { n, err := w.Write([]byte("abcdefghij")) done <- ioret{n, err} }() cancel() select { case ret := <-done: if ret.n != 0 { t.Error("ret.n should be 0", ret.n) } if ret.err == nil { t.Error("ret.err should be ctx error", ret.err) } case <-time.After(20 * time.Millisecond): t.Fatal("failed to stop writing after cancel") } } func TestReadPostCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) piper, pipew := io.Pipe() r := NewReader(ctx, piper) buf := make([]byte, 10) done := make(chan ioret) go func() { n, err := r.Read(buf) done <- ioret{n, err} }() cancel() select { case ret := <-done: if ret.n != 0 { t.Error("ret.n should be 0", ret.n) } if ret.err == nil { t.Error("ret.err should be ctx error", ret.err) } case <-time.After(20 * time.Millisecond): t.Fatal("failed to stop reading after cancel") } pipew.Write([]byte("abcdefghij")) if !bytes.Equal(buf, make([]byte, len(buf))) { t.Fatal("buffer should have not been written to") } } func TestWritePostCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) piper, pipew := io.Pipe() w := NewWriter(ctx, pipew) buf := []byte("abcdefghij") buf2 := make([]byte, 10) done := make(chan ioret) go func() { n, err := w.Write(buf) done <- ioret{n, err} }() piper.Read(buf2) select { case ret := <-done: if ret.n != 10 { t.Error("ret.n should be 10", ret.n) } if ret.err != nil { t.Error("ret.err should be nil", ret.err) } if string(buf2) != "abcdefghij" { t.Error("write contents differ") } case <-time.After(20 * time.Millisecond): t.Fatal("failed to write") } go func() { n, err := w.Write(buf) done <- ioret{n, err} }() cancel() select { case ret := <-done: if ret.n != 0 { t.Error("ret.n should be 0", ret.n) } if ret.err == nil { t.Error("ret.err should be ctx error", ret.err) } case <-time.After(20 * time.Millisecond): t.Fatal("failed to stop writing after cancel") } copy(buf, []byte("aaaaaaaaaa")) piper.Read(buf2) if string(buf2) == "aaaaaaaaaa" { t.Error("buffer was read from after ctx cancel") } else if string(buf2) != "abcdefghij" { t.Error("write contents differ from expected") } }