Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
9
vendor/github.com/jbenet/goprocess/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/jbenet/goprocess/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.12
|
||||
|
||||
script:
|
||||
- go test -race -v ./...
|
||||
21
vendor/github.com/jbenet/goprocess/LICENSE
generated
vendored
Normal file
21
vendor/github.com/jbenet/goprocess/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The 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.
|
||||
132
vendor/github.com/jbenet/goprocess/README.md
generated
vendored
Normal file
132
vendor/github.com/jbenet/goprocess/README.md
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
# goprocess - lifecycles in go
|
||||
|
||||
[](https://travis-ci.org/jbenet/goprocess)
|
||||
|
||||
(Based on https://github.com/jbenet/go-ctxgroup)
|
||||
|
||||
- Godoc: https://godoc.org/github.com/jbenet/goprocess
|
||||
|
||||
`goprocess` introduces a way to manage process lifecycles in go. It is
|
||||
much like [go.net/context](https://godoc.org/code.google.com/p/go.net/context)
|
||||
(it actually uses a Context), but it is more like a Context-WaitGroup hybrid.
|
||||
`goprocess` is about being able to start and stop units of work, which may
|
||||
receive `Close` signals from many clients. Think of it like a UNIX process
|
||||
tree, but inside go.
|
||||
|
||||
`goprocess` seeks to minimally affect your objects, so you can use it
|
||||
with both embedding or composition. At the heart of `goprocess` is the
|
||||
`Process` interface:
|
||||
|
||||
```Go
|
||||
// Process is the basic unit of work in goprocess. It defines a computation
|
||||
// with a lifecycle:
|
||||
// - running (before calling Close),
|
||||
// - closing (after calling Close at least once),
|
||||
// - closed (after Close returns, and all teardown has _completed_).
|
||||
//
|
||||
// More specifically, it fits this:
|
||||
//
|
||||
// p := WithTeardown(tf) // new process is created, it is now running.
|
||||
// p.AddChild(q) // can register children **before** Closing.
|
||||
// go p.Close() // blocks until done running teardown func.
|
||||
// <-p.Closing() // would now return true.
|
||||
// <-p.childrenDone() // wait on all children to be done
|
||||
// p.teardown() // runs the user's teardown function tf.
|
||||
// p.Close() // now returns, with error teardown returned.
|
||||
// <-p.Closed() // would now return true.
|
||||
//
|
||||
// Processes can be arranged in a process "tree", where children are
|
||||
// automatically Closed if their parents are closed. (Note, it is actually
|
||||
// a Process DAG, children may have multiple parents). A process may also
|
||||
// optionally wait for another to fully Close before beginning to Close.
|
||||
// This makes it easy to ensure order of operations and proper sequential
|
||||
// teardown of resurces. For example:
|
||||
//
|
||||
// p1 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 1")
|
||||
// })
|
||||
// p2 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 2")
|
||||
// })
|
||||
// p3 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 3")
|
||||
// })
|
||||
//
|
||||
// p1.AddChild(p2)
|
||||
// p2.AddChild(p3)
|
||||
//
|
||||
//
|
||||
// go p1.Close()
|
||||
// go p2.Close()
|
||||
// go p3.Close()
|
||||
//
|
||||
// // Output:
|
||||
// // closing 3
|
||||
// // closing 2
|
||||
// // closing 1
|
||||
//
|
||||
// Process is modelled after the UNIX processes group idea, and heavily
|
||||
// informed by sync.WaitGroup and go.net/context.Context.
|
||||
//
|
||||
// In the function documentation of this interface, `p` always refers to
|
||||
// the self Process.
|
||||
type Process interface {
|
||||
|
||||
// WaitFor makes p wait for q before exiting. Thus, p will _always_ close
|
||||
// _after_ q. Note well: a waiting cycle is deadlock.
|
||||
//
|
||||
// If q is already Closed, WaitFor calls p.Close()
|
||||
// If p is already Closing or Closed, WaitFor panics. This is the same thing
|
||||
// as calling Add(1) _after_ calling Done() on a wait group. Calling WaitFor
|
||||
// on an already-closed process is a programming error likely due to bad
|
||||
// synchronization
|
||||
WaitFor(q Process)
|
||||
|
||||
// AddChildNoWait registers child as a "child" of Process. As in UNIX,
|
||||
// when parent is Closed, child is Closed -- child may Close beforehand.
|
||||
// This is the equivalent of calling:
|
||||
//
|
||||
// go func(parent, child Process) {
|
||||
// <-parent.Closing()
|
||||
// child.Close()
|
||||
// }(p, q)
|
||||
//
|
||||
// Note: the naming of functions is `AddChildNoWait` and `AddChild` (instead
|
||||
// of `AddChild` and `AddChildWaitFor`) because:
|
||||
// - it is the more common operation,
|
||||
// - explicitness is helpful in the less common case (no waiting), and
|
||||
// - usual "child" semantics imply parent Processes should wait for children.
|
||||
AddChildNoWait(q Process)
|
||||
|
||||
// AddChild is the equivalent of calling:
|
||||
// parent.AddChildNoWait(q)
|
||||
// parent.WaitFor(q)
|
||||
AddChild(q Process)
|
||||
|
||||
// Go creates a new process, adds it as a child, and spawns the ProcessFunc f
|
||||
// in its own goroutine. It is equivalent to:
|
||||
//
|
||||
// GoChild(p, f)
|
||||
//
|
||||
// It is useful to construct simple asynchronous workers, children of p.
|
||||
Go(f ProcessFunc) Process
|
||||
|
||||
// Close ends the process. Close blocks until the process has completely
|
||||
// shut down, and any teardown has run _exactly once_. The returned error
|
||||
// is available indefinitely: calling Close twice returns the same error.
|
||||
// If the process has already been closed, Close returns immediately.
|
||||
Close() error
|
||||
|
||||
// Closing is a signal to wait upon. The returned channel is closed
|
||||
// _after_ Close has been called at least once, but teardown may or may
|
||||
// not be done yet. The primary use case of Closing is for children who
|
||||
// need to know when a parent is shutting down, and therefore also shut
|
||||
// down.
|
||||
Closing() <-chan struct{}
|
||||
|
||||
// Closed is a signal to wait upon. The returned channel is closed
|
||||
// _after_ Close has completed; teardown has finished. The primary use case
|
||||
// of Closed is waiting for a Process to Close without _causing_ the Close.
|
||||
Closed() <-chan struct{}
|
||||
}
|
||||
```
|
||||
33
vendor/github.com/jbenet/goprocess/background.go
generated
vendored
Normal file
33
vendor/github.com/jbenet/goprocess/background.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package goprocess
|
||||
|
||||
// Background returns the "bgProcess" Process: a statically allocated
|
||||
// process that can _never_ close. It also never enters Closing() state.
|
||||
// Calling Background().Close() will hang indefinitely.
|
||||
func Background() Process {
|
||||
return background
|
||||
}
|
||||
|
||||
var background = new(bgProcess)
|
||||
|
||||
type bgProcess struct{}
|
||||
|
||||
func (*bgProcess) WaitFor(q Process) {}
|
||||
func (*bgProcess) AddChildNoWait(q Process) {}
|
||||
func (*bgProcess) AddChild(q Process) {}
|
||||
func (*bgProcess) Close() error { select {} }
|
||||
func (*bgProcess) CloseAfterChildren() error { select {} }
|
||||
func (*bgProcess) Closing() <-chan struct{} { return nil }
|
||||
func (*bgProcess) Closed() <-chan struct{} { return nil }
|
||||
func (*bgProcess) Err() error { select {} }
|
||||
|
||||
func (*bgProcess) SetTeardown(tf TeardownFunc) {
|
||||
panic("can't set teardown on bgProcess process")
|
||||
}
|
||||
func (*bgProcess) Go(f ProcessFunc) Process {
|
||||
child := newProcess(nil)
|
||||
go func() {
|
||||
f(child)
|
||||
child.Close()
|
||||
}()
|
||||
return child
|
||||
}
|
||||
263
vendor/github.com/jbenet/goprocess/goprocess.go
generated
vendored
Normal file
263
vendor/github.com/jbenet/goprocess/goprocess.go
generated
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
// Package goprocess introduces a Process abstraction that allows simple
|
||||
// organization, and orchestration of work. It is much like a WaitGroup,
|
||||
// and much like a context.Context, but also ensures safe **exactly-once**,
|
||||
// and well-ordered teardown semantics.
|
||||
package goprocess
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
// Process is the basic unit of work in goprocess. It defines a computation
|
||||
// with a lifecycle:
|
||||
// - running (before calling Close),
|
||||
// - closing (after calling Close at least once),
|
||||
// - closed (after Close returns, and all teardown has _completed_).
|
||||
//
|
||||
// More specifically, it fits this:
|
||||
//
|
||||
// p := WithTeardown(tf) // new process is created, it is now running.
|
||||
// p.AddChild(q) // can register children **before** Closed().
|
||||
// go p.Close() // blocks until done running teardown func.
|
||||
// <-p.Closing() // would now return true.
|
||||
// <-p.childrenDone() // wait on all children to be done
|
||||
// p.teardown() // runs the user's teardown function tf.
|
||||
// p.Close() // now returns, with error teardown returned.
|
||||
// <-p.Closed() // would now return true.
|
||||
//
|
||||
// Processes can be arranged in a process "tree", where children are
|
||||
// automatically Closed if their parents are closed. (Note, it is actually
|
||||
// a Process DAG, children may have multiple parents). A process may also
|
||||
// optionally wait for another to fully Close before beginning to Close.
|
||||
// This makes it easy to ensure order of operations and proper sequential
|
||||
// teardown of resurces. For example:
|
||||
//
|
||||
// p1 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 1")
|
||||
// })
|
||||
// p2 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 2")
|
||||
// })
|
||||
// p3 := goprocess.WithTeardown(func() error {
|
||||
// fmt.Println("closing 3")
|
||||
// })
|
||||
//
|
||||
// p1.AddChild(p2)
|
||||
// p2.AddChild(p3)
|
||||
//
|
||||
//
|
||||
// go p1.Close()
|
||||
// go p2.Close()
|
||||
// go p3.Close()
|
||||
//
|
||||
// // Output:
|
||||
// // closing 3
|
||||
// // closing 2
|
||||
// // closing 1
|
||||
//
|
||||
// Process is modelled after the UNIX processes group idea, and heavily
|
||||
// informed by sync.WaitGroup and go.net/context.Context.
|
||||
//
|
||||
// In the function documentation of this interface, `p` always refers to
|
||||
// the self Process.
|
||||
type Process interface {
|
||||
|
||||
// WaitFor makes p wait for q before exiting. Thus, p will _always_ close
|
||||
// _after_ q. Note well: a waiting cycle is deadlock.
|
||||
//
|
||||
// If p is already Closed, WaitFor panics. This is the same thing as
|
||||
// calling Add(1) _after_ calling Done() on a wait group. Calling
|
||||
// WaitFor on an already-closed process is a programming error likely
|
||||
// due to bad synchronization
|
||||
WaitFor(q Process)
|
||||
|
||||
// AddChildNoWait registers child as a "child" of Process. As in UNIX,
|
||||
// when parent is Closed, child is Closed -- child may Close beforehand.
|
||||
// This is the equivalent of calling:
|
||||
//
|
||||
// go func(parent, child Process) {
|
||||
// <-parent.Closing()
|
||||
// child.Close()
|
||||
// }(p, q)
|
||||
//
|
||||
// Note: the naming of functions is `AddChildNoWait` and `AddChild` (instead
|
||||
// of `AddChild` and `AddChildWaitFor`) because:
|
||||
// - it is the more common operation,
|
||||
// - explicitness is helpful in the less common case (no waiting), and
|
||||
// - usual "child" semantics imply parent Processes should wait for children.
|
||||
AddChildNoWait(q Process)
|
||||
|
||||
// AddChild is the equivalent of calling:
|
||||
// parent.AddChildNoWait(q)
|
||||
// parent.WaitFor(q)
|
||||
//
|
||||
// It will _panic_ if the parent is already closed.
|
||||
AddChild(q Process)
|
||||
|
||||
// Go is much like `go`, as it runs a function in a newly spawned goroutine.
|
||||
// The neat part of Process.Go is that the Process object you call it on will:
|
||||
// * construct a child Process, and call AddChild(child) on it
|
||||
// * spawn a goroutine, and call the given function
|
||||
// * Close the child when the function exits.
|
||||
// This way, you can rest assured each goroutine you spawn has its very own
|
||||
// Process context, and that it will be closed when the function exits.
|
||||
// It is the function's responsibility to respect the Closing of its Process,
|
||||
// namely it should exit (return) when <-Closing() is ready. It is basically:
|
||||
//
|
||||
// func (p Process) Go(f ProcessFunc) Process {
|
||||
// child := WithParent(p)
|
||||
// go func () {
|
||||
// f(child)
|
||||
// child.Close()
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
// It is useful to construct simple asynchronous workers, children of p.
|
||||
Go(f ProcessFunc) Process
|
||||
|
||||
// SetTeardown sets the process's teardown to tf.
|
||||
SetTeardown(tf TeardownFunc)
|
||||
|
||||
// Close ends the process. Close blocks until the process has completely
|
||||
// shut down, and any teardown has run _exactly once_. The returned error
|
||||
// is available indefinitely: calling Close twice returns the same error.
|
||||
// If the process has already been closed, Close returns immediately.
|
||||
Close() error
|
||||
|
||||
// CloseAfterChildren calls Close _after_ its children have Closed
|
||||
// normally (i.e. it _does not_ attempt to close them).
|
||||
CloseAfterChildren() error
|
||||
|
||||
// Closing is a signal to wait upon. The returned channel is closed
|
||||
// _after_ Close has been called at least once, but teardown may or may
|
||||
// not be done yet. The primary use case of Closing is for children who
|
||||
// need to know when a parent is shutting down, and therefore also shut
|
||||
// down.
|
||||
Closing() <-chan struct{}
|
||||
|
||||
// Closed is a signal to wait upon. The returned channel is closed
|
||||
// _after_ Close has completed; teardown has finished. The primary use case
|
||||
// of Closed is waiting for a Process to Close without _causing_ the Close.
|
||||
Closed() <-chan struct{}
|
||||
|
||||
// Err waits until the process is closed, and then returns any error that
|
||||
// occurred during shutdown.
|
||||
Err() error
|
||||
}
|
||||
|
||||
// TeardownFunc is a function used to cleanup state at the end of the
|
||||
// lifecycle of a Process.
|
||||
type TeardownFunc func() error
|
||||
|
||||
// ProcessFunc is a function that takes a process. Its main use case is goprocess.Go,
|
||||
// which spawns a ProcessFunc in its own goroutine, and returns a corresponding
|
||||
// Process object.
|
||||
type ProcessFunc func(proc Process)
|
||||
|
||||
var nilProcessFunc = func(Process) {}
|
||||
|
||||
// Go is much like `go`: it runs a function in a newly spawned goroutine. The neat
|
||||
// part of Go is that it provides Process object to communicate between the
|
||||
// function and the outside world. Thus, callers can easily WaitFor, or Close the
|
||||
// function. It is the function's responsibility to respect the Closing of its Process,
|
||||
// namely it should exit (return) when <-Closing() is ready. It is simply:
|
||||
//
|
||||
// func Go(f ProcessFunc) Process {
|
||||
// p := WithParent(Background())
|
||||
// p.Go(f)
|
||||
// return p
|
||||
// }
|
||||
//
|
||||
// Note that a naive implementation of Go like the following would not work:
|
||||
//
|
||||
// func Go(f ProcessFunc) Process {
|
||||
// return Background().Go(f)
|
||||
// }
|
||||
//
|
||||
// This is because having the process you
|
||||
func Go(f ProcessFunc) Process {
|
||||
// return GoChild(Background(), f)
|
||||
|
||||
// we use two processes, one for communication, and
|
||||
// one for ensuring we wait on the function (unclosable from the outside).
|
||||
p := newProcess(nil)
|
||||
waitFor := newProcess(nil)
|
||||
p.WaitFor(waitFor) // prevent p from closing
|
||||
go func() {
|
||||
f(p)
|
||||
waitFor.Close() // allow p to close.
|
||||
p.Close() // ensure p closes.
|
||||
}()
|
||||
return p
|
||||
}
|
||||
|
||||
// GoChild is like Go, but it registers the returned Process as a child of parent,
|
||||
// **before** spawning the goroutine, which ensures proper synchronization with parent.
|
||||
// It is somewhat like
|
||||
//
|
||||
// func GoChild(parent Process, f ProcessFunc) Process {
|
||||
// p := WithParent(parent)
|
||||
// p.Go(f)
|
||||
// return p
|
||||
// }
|
||||
//
|
||||
// And it is similar to the classic WaitGroup use case:
|
||||
//
|
||||
// func WaitGroupGo(wg sync.WaitGroup, child func()) {
|
||||
// wg.Add(1)
|
||||
// go func() {
|
||||
// child()
|
||||
// wg.Done()
|
||||
// }()
|
||||
// }
|
||||
//
|
||||
func GoChild(parent Process, f ProcessFunc) Process {
|
||||
p := WithParent(parent)
|
||||
p.Go(f)
|
||||
return p
|
||||
}
|
||||
|
||||
// Spawn is an alias of `Go`. In many contexts, Spawn is a
|
||||
// well-known Process launching word, which fits our use case.
|
||||
var Spawn = Go
|
||||
|
||||
// SpawnChild is an alias of `GoChild`. In many contexts, Spawn is a
|
||||
// well-known Process launching word, which fits our use case.
|
||||
var SpawnChild = GoChild
|
||||
|
||||
// WithTeardown constructs and returns a Process with a TeardownFunc.
|
||||
// TeardownFunc tf will be called **exactly-once** when Process is
|
||||
// Closing, after all Children have fully closed, and before p is Closed.
|
||||
// In fact, Process p will not be Closed until tf runs and exits.
|
||||
// See lifecycle in Process doc.
|
||||
func WithTeardown(tf TeardownFunc) Process {
|
||||
if tf == nil {
|
||||
panic("nil tf TeardownFunc")
|
||||
}
|
||||
return newProcess(tf)
|
||||
}
|
||||
|
||||
// WithParent constructs and returns a Process with a given parent.
|
||||
func WithParent(parent Process) Process {
|
||||
if parent == nil {
|
||||
panic("nil parent Process")
|
||||
}
|
||||
q := newProcess(nil)
|
||||
parent.AddChild(q)
|
||||
return q
|
||||
}
|
||||
|
||||
// WithSignals returns a Process that will Close() when any given signal fires.
|
||||
// This is useful to bind Process trees to syscall.SIGTERM, SIGKILL, etc.
|
||||
func WithSignals(sig ...os.Signal) Process {
|
||||
p := WithParent(Background())
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, sig...)
|
||||
go func() {
|
||||
<-c
|
||||
signal.Stop(c)
|
||||
p.Close()
|
||||
}()
|
||||
return p
|
||||
}
|
||||
299
vendor/github.com/jbenet/goprocess/impl-mutex.go
generated
vendored
Normal file
299
vendor/github.com/jbenet/goprocess/impl-mutex.go
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
package goprocess
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// process implements Process
|
||||
type process struct {
|
||||
children map[*processLink]struct{} // process to close with us
|
||||
waitfors map[*processLink]struct{} // process to only wait for
|
||||
waiters []*processLink // processes that wait for us. for gc.
|
||||
|
||||
teardown TeardownFunc // called to run the teardown logic.
|
||||
closing chan struct{} // closed once close starts.
|
||||
closed chan struct{} // closed once close is done.
|
||||
closeErr error // error to return to clients of Close()
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// newProcess constructs and returns a Process.
|
||||
// It will call tf TeardownFunc exactly once:
|
||||
// **after** all children have fully Closed,
|
||||
// **after** entering <-Closing(), and
|
||||
// **before** <-Closed().
|
||||
func newProcess(tf TeardownFunc) *process {
|
||||
return &process{
|
||||
teardown: tf,
|
||||
closed: make(chan struct{}),
|
||||
closing: make(chan struct{}),
|
||||
waitfors: make(map[*processLink]struct{}),
|
||||
children: make(map[*processLink]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *process) WaitFor(q Process) {
|
||||
if q == nil {
|
||||
panic("waiting for nil process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
select {
|
||||
case <-p.Closed():
|
||||
panic("Process cannot wait after being closed")
|
||||
default:
|
||||
}
|
||||
|
||||
pl := newProcessLink(p, q)
|
||||
if p.waitfors == nil {
|
||||
// This may be nil when we're closing. In close, we'll keep
|
||||
// reading this map till it stays nil.
|
||||
p.waitfors = make(map[*processLink]struct{}, 1)
|
||||
}
|
||||
p.waitfors[pl] = struct{}{}
|
||||
go pl.AddToChild()
|
||||
}
|
||||
|
||||
func (p *process) AddChildNoWait(child Process) {
|
||||
if child == nil {
|
||||
panic("adding nil child process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
select {
|
||||
case <-p.Closing():
|
||||
// Either closed or closing, close child immediately. This is
|
||||
// correct because we aren't asked to _wait_ on this child.
|
||||
go child.Close()
|
||||
// Wait for the child to start closing so the child is in the
|
||||
// "correct" state after this function finishes (see #17).
|
||||
<-child.Closing()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
pl := newProcessLink(p, child)
|
||||
p.children[pl] = struct{}{}
|
||||
go pl.AddToChild()
|
||||
}
|
||||
|
||||
func (p *process) AddChild(child Process) {
|
||||
if child == nil {
|
||||
panic("adding nil child process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
pl := newProcessLink(p, child)
|
||||
|
||||
select {
|
||||
case <-p.Closed():
|
||||
// AddChild must not be called on a dead process. Maybe that's
|
||||
// too strict?
|
||||
panic("Process cannot add children after being closed")
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.Closing():
|
||||
// Already closing, close child in background.
|
||||
go child.Close()
|
||||
// Wait for the child to start closing so the child is in the
|
||||
// "correct" state after this function finishes (see #17).
|
||||
<-child.Closing()
|
||||
default:
|
||||
// Only add the child when not closing. When closing, just add
|
||||
// it to the "waitfors" list.
|
||||
p.children[pl] = struct{}{}
|
||||
}
|
||||
|
||||
if p.waitfors == nil {
|
||||
// This may be be nil when we're closing. In close, we'll keep
|
||||
// reading this map till it stays nil.
|
||||
p.waitfors = make(map[*processLink]struct{}, 1)
|
||||
}
|
||||
p.waitfors[pl] = struct{}{}
|
||||
go pl.AddToChild()
|
||||
}
|
||||
|
||||
func (p *process) Go(f ProcessFunc) Process {
|
||||
child := newProcess(nil)
|
||||
waitFor := newProcess(nil)
|
||||
child.WaitFor(waitFor) // prevent child from closing
|
||||
|
||||
// add child last, to prevent a closing parent from
|
||||
// closing all of them prematurely, before running the func.
|
||||
p.AddChild(child)
|
||||
go func() {
|
||||
f(child)
|
||||
waitFor.Close() // allow child to close.
|
||||
child.CloseAfterChildren() // close to tear down.
|
||||
}()
|
||||
return child
|
||||
}
|
||||
|
||||
// SetTeardown to assign a teardown function
|
||||
func (p *process) SetTeardown(tf TeardownFunc) {
|
||||
if tf == nil {
|
||||
panic("cannot set nil TeardownFunc")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
if p.teardown != nil {
|
||||
panic("cannot SetTeardown twice")
|
||||
}
|
||||
|
||||
p.teardown = tf
|
||||
select {
|
||||
case <-p.Closed():
|
||||
// Call the teardown function, but don't set the error. We can't
|
||||
// change that after we shut down.
|
||||
tf()
|
||||
default:
|
||||
}
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
// Close is the external close function.
|
||||
// it's a wrapper around internalClose that waits on Closed()
|
||||
func (p *process) Close() error {
|
||||
p.Lock()
|
||||
|
||||
// if already closing, or closed, get out. (but wait!)
|
||||
select {
|
||||
case <-p.Closing():
|
||||
p.Unlock()
|
||||
<-p.Closed()
|
||||
return p.closeErr
|
||||
default:
|
||||
}
|
||||
|
||||
p.doClose()
|
||||
p.Unlock()
|
||||
return p.closeErr
|
||||
}
|
||||
|
||||
func (p *process) Closing() <-chan struct{} {
|
||||
return p.closing
|
||||
}
|
||||
|
||||
func (p *process) Closed() <-chan struct{} {
|
||||
return p.closed
|
||||
}
|
||||
|
||||
func (p *process) Err() error {
|
||||
<-p.Closed()
|
||||
return p.closeErr
|
||||
}
|
||||
|
||||
// the _actual_ close process.
|
||||
func (p *process) doClose() {
|
||||
// this function is only be called once (protected by p.Lock()).
|
||||
// and it will panic (on closing channels) otherwise.
|
||||
|
||||
close(p.closing) // signal that we're shutting down (Closing)
|
||||
|
||||
// We won't add any children after we start closing so we can do this
|
||||
// once.
|
||||
for plc, _ := range p.children {
|
||||
child := plc.Child()
|
||||
if child != nil { // check because child may already have been removed.
|
||||
go child.Close() // force all children to shut down
|
||||
}
|
||||
|
||||
// safe to call multiple times per link
|
||||
plc.ParentClear()
|
||||
}
|
||||
p.children = nil // clear them. release memory.
|
||||
|
||||
// We may repeatedly continue to add waiters while we wait to close so
|
||||
// we have to do this in a loop.
|
||||
for len(p.waitfors) > 0 {
|
||||
// we must be careful not to iterate over waitfors directly, as it may
|
||||
// change under our feet.
|
||||
wf := p.waitfors
|
||||
p.waitfors = nil // clear them. release memory.
|
||||
for w, _ := range wf {
|
||||
// Here, we wait UNLOCKED, so that waitfors who are in the middle of
|
||||
// adding a child to us can finish. we will immediately close the child.
|
||||
p.Unlock()
|
||||
<-w.ChildClosed() // wait till all waitfors are fully closed (before teardown)
|
||||
p.Lock()
|
||||
|
||||
// safe to call multiple times per link
|
||||
w.ParentClear()
|
||||
}
|
||||
}
|
||||
|
||||
if p.teardown != nil {
|
||||
p.closeErr = p.teardown() // actually run the close logic (ok safe to teardown)
|
||||
}
|
||||
close(p.closed) // signal that we're shut down (Closed)
|
||||
|
||||
// go remove all the parents from the process links. optimization.
|
||||
go func(waiters []*processLink) {
|
||||
for _, pl := range waiters {
|
||||
pl.ClearChild()
|
||||
pr, ok := pl.Parent().(*process)
|
||||
if !ok {
|
||||
// parent has already been called to close
|
||||
continue
|
||||
}
|
||||
pr.Lock()
|
||||
delete(pr.waitfors, pl)
|
||||
delete(pr.children, pl)
|
||||
pr.Unlock()
|
||||
}
|
||||
}(p.waiters) // pass in so
|
||||
p.waiters = nil // clear them. release memory.
|
||||
}
|
||||
|
||||
// We will only wait on the children we have now.
|
||||
// We will not wait on children added subsequently.
|
||||
// this may change in the future.
|
||||
func (p *process) CloseAfterChildren() error {
|
||||
p.Lock()
|
||||
select {
|
||||
case <-p.Closed():
|
||||
p.Unlock()
|
||||
return p.Close() // get error. safe, after p.Closed()
|
||||
default:
|
||||
}
|
||||
p.Unlock()
|
||||
|
||||
// here only from one goroutine.
|
||||
|
||||
nextToWaitFor := func() Process {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
for e, _ := range p.waitfors {
|
||||
c := e.Child()
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-c.Closed():
|
||||
default:
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wait for all processes we're waiting for are closed.
|
||||
// the semantics here are simple: we will _only_ close
|
||||
// if there are no processes currently waiting for.
|
||||
for next := nextToWaitFor(); next != nil; next = nextToWaitFor() {
|
||||
<-next.Closed()
|
||||
}
|
||||
|
||||
// YAY! we're done. close
|
||||
return p.Close()
|
||||
}
|
||||
128
vendor/github.com/jbenet/goprocess/link.go
generated
vendored
Normal file
128
vendor/github.com/jbenet/goprocess/link.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package goprocess
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// closedCh is an alread-closed channel. used to return
|
||||
// in cases where we already know a channel is closed.
|
||||
var closedCh chan struct{}
|
||||
|
||||
func init() {
|
||||
closedCh = make(chan struct{})
|
||||
close(closedCh)
|
||||
}
|
||||
|
||||
// a processLink is an internal bookkeeping datastructure.
|
||||
// it's used to form a relationship between two processes.
|
||||
// It is mostly for keeping memory usage down (letting
|
||||
// children close and be garbage-collected).
|
||||
type processLink struct {
|
||||
// guards all fields.
|
||||
// DO NOT HOLD while holding process locks.
|
||||
// it may be slow, and could deadlock if not careful.
|
||||
sync.Mutex
|
||||
parent Process
|
||||
child Process
|
||||
}
|
||||
|
||||
func newProcessLink(p, c Process) *processLink {
|
||||
return &processLink{
|
||||
parent: p,
|
||||
child: c,
|
||||
}
|
||||
}
|
||||
|
||||
// Closing returns whether the child is closing
|
||||
func (pl *processLink) ChildClosing() <-chan struct{} {
|
||||
// grab a hold of it, and unlock, as .Closing may block.
|
||||
pl.Lock()
|
||||
child := pl.child
|
||||
pl.Unlock()
|
||||
|
||||
if child == nil { // already closed? memory optimization.
|
||||
return closedCh
|
||||
}
|
||||
return child.Closing()
|
||||
}
|
||||
|
||||
func (pl *processLink) ChildClosed() <-chan struct{} {
|
||||
// grab a hold of it, and unlock, as .Closed may block.
|
||||
pl.Lock()
|
||||
child := pl.child
|
||||
pl.Unlock()
|
||||
|
||||
if child == nil { // already closed? memory optimization.
|
||||
return closedCh
|
||||
}
|
||||
return child.Closed()
|
||||
}
|
||||
|
||||
func (pl *processLink) ChildClose() {
|
||||
// grab a hold of it, and unlock, as .Closed may block.
|
||||
pl.Lock()
|
||||
child := pl.child
|
||||
pl.Unlock()
|
||||
|
||||
if child != nil { // already closed? memory optimization.
|
||||
child.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *processLink) ClearChild() {
|
||||
pl.Lock()
|
||||
pl.child = nil
|
||||
pl.Unlock()
|
||||
}
|
||||
|
||||
func (pl *processLink) ParentClear() {
|
||||
pl.Lock()
|
||||
pl.parent = nil
|
||||
pl.Unlock()
|
||||
}
|
||||
|
||||
func (pl *processLink) Child() Process {
|
||||
pl.Lock()
|
||||
defer pl.Unlock()
|
||||
return pl.child
|
||||
}
|
||||
|
||||
func (pl *processLink) Parent() Process {
|
||||
pl.Lock()
|
||||
defer pl.Unlock()
|
||||
return pl.parent
|
||||
}
|
||||
|
||||
func (pl *processLink) AddToChild() {
|
||||
cp := pl.Child()
|
||||
|
||||
// is it a *process ? if not... panic.
|
||||
var c *process
|
||||
switch cp := cp.(type) {
|
||||
case *process:
|
||||
c = cp
|
||||
case *bgProcess:
|
||||
// Background process never closes so we don't need to do
|
||||
// anything.
|
||||
return
|
||||
default:
|
||||
panic("goprocess does not yet support other process impls.")
|
||||
}
|
||||
|
||||
// first, is it Closed?
|
||||
c.Lock()
|
||||
select {
|
||||
case <-c.Closed():
|
||||
c.Unlock()
|
||||
|
||||
// already closed. must not add.
|
||||
// we must clear it, though. do so without the lock.
|
||||
pl.ClearChild()
|
||||
return
|
||||
|
||||
default:
|
||||
// put the process link into q's waiters
|
||||
c.waiters = append(c.waiters, pl)
|
||||
c.Unlock()
|
||||
}
|
||||
}
|
||||
14
vendor/github.com/jbenet/goprocess/package.json
generated
vendored
Normal file
14
vendor/github.com/jbenet/goprocess/package.json
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"author": "whyrusleeping",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jbenet/goprocess"
|
||||
},
|
||||
"gx": {
|
||||
"dvcsimport": "github.com/jbenet/goprocess"
|
||||
},
|
||||
"gxVersion": "0.8.0",
|
||||
"language": "go",
|
||||
"license": "",
|
||||
"name": "goprocess",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
Reference in New Issue
Block a user