Created
March 15, 2017 13:23
-
-
Save sdboyer/a3962742624e5e55104cfdbcec38d16f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package gps | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"net/url" | |
"sync" | |
radix "github.com/armon/go-radix" | |
) | |
type srcCommand interface { | |
} | |
type callManager struct { | |
} | |
type sourcesCompany struct { | |
actionChan chan func() | |
ctx context.Context | |
callMgr callManager | |
srcs map[string]sourceActor | |
deducer *deductionCoordinator | |
} | |
func (sc *sourcesCompany) getSourceFor(id ProjectIdentifier) (sourceActor, error) { | |
normalizedName := id.normalizedSource() | |
type ret struct { | |
sa sourceActor | |
err error | |
} | |
retchan := make(chan ret, 0) | |
sc.actionChan <- func() { | |
// Simple case - actor exists for this source, send it back right away | |
if srcActor, has := sa.srcs[normalizedName]; has { | |
retchan <- ret{sa: srcActor} | |
return | |
} | |
// No actor for this path yet. Set one up. | |
pd, err := sc.deducer.deduceRootPath(normalizedName) | |
if err != nil { | |
// As in the deducer, don't cache errors to make externally-driven | |
// retry strategies feasible. | |
retchan <- ret{err: err} | |
return | |
} | |
srcActor := sourceActor{} | |
} | |
retval := <-retchan | |
return retval.sa, retval.err | |
} | |
// sourceActors act as a gateway to all calls for data from sources. | |
type sourceActor struct { | |
maybe maybeSource | |
} | |
type deductionCoordinator struct { | |
ctx context.Context | |
callMgr callManager | |
rootxt *radix.Tree | |
deducext *deducerTrie | |
actionChan chan func() | |
} | |
func newDeductionCoordinator(ctx context.Context, cm callManager) *deductionCoordinator { | |
dc := &deductionCoordinator{ | |
ctx: ctx, | |
callMgr: cm, | |
rootxt: radix.New(), | |
deducext: pathDeducerTrie(), | |
} | |
// Start listener loop | |
go func() { | |
for { | |
select { | |
case <-ctx.Done(): | |
// TODO should this iterate over the rootxt and kill open vda? | |
close(dc.actionChan) | |
case action := <-dc.actionChan: | |
action() | |
} | |
} | |
}() | |
return dc | |
} | |
func (dc *deductionCoordinator) deduceRootPath(path string) (pathDeduction, error) { | |
retchan := make(chan interface{}, 0) | |
dc.actionChan <- func() { | |
vdaDeduce := func(vda *vanityDeducerActor) { | |
pd, err := vda.deduce(path) | |
if err != nil { | |
retchan <- err | |
} else { | |
retchan <- pd | |
} | |
} | |
// First, check the rootxt to see if there's a prefix match - if so, we | |
// can return that and move on. | |
if prefix, data, has := dc.rootxt.LongestPrefix(path); has && isPathPrefixOrEqual(prefix, path) { | |
switch d := data.(type) { | |
case maybeSource: | |
retchan <- pathDeduction{root: prefix, mb: d} | |
case *vanityDeducerActor: | |
// Multiple calls have come in for a similar path shape during | |
// the window in which the HTTP request to retrieve go get | |
// metadata is in flight. Fold this request in with the existing | |
// one(s) by giving it its own goroutine that awaits a response | |
// from the running vanityDeducerActor. | |
go vdaDeduce(d) | |
default: | |
panic(fmt.Sprintf("unexpected %T in deductionCoordinator.rootxt: %v", d, d)) | |
} | |
// Finding either a finished maybeSource or an in-flight vanity | |
// deduction means there's nothing more to do on this action. | |
return | |
} | |
// No match. Try known path deduction first. | |
pd, err := dc.deduceKnownPaths(path) | |
if err == nil { | |
// Deduction worked; store it in the rootxt, send on retchan and | |
// terminate. | |
// FIXME deal with changing path vs. root. Probably needs to be | |
// predeclared and reused in the vda returnFunc | |
dc.rootxt.Insert(pd.root, pd.mb) | |
retchan <- pd | |
return | |
} | |
if err != noKnownPathMatch { | |
retchan <- err | |
return | |
} | |
// The err indicates no known path matched. It's still possible that | |
// retrieving go get metadata might do the trick. | |
vda := &vanityDeducerActor{ | |
basePath: path, | |
callMgr: dc.callMgr, | |
ctx: dc.ctx, | |
// The vanity deducer will call this func with a completed | |
// pathDeduction if it succeeds in finding one. We process it | |
// back through the action channel to ensure serialized | |
// access to the rootxt map. | |
returnFunc: func(pd pathDeduction) { | |
dc.actionChan <- func() { | |
if pd.root != path { | |
// Clean out the vanity deducer, we don't need it | |
// anymore. | |
dc.rootxt.Delete(path) | |
} | |
dc.rootxt.Insert(pd.root, pd.mb) | |
} | |
}, | |
} | |
// Save the vda in the rootxt so that calls checking on similar | |
// paths made while the request is in flight can be folded together. | |
dc.rootxt.Insert(path, vda) | |
// Spawn a new goroutine for the HTTP-backed deduction process. | |
go vdaDeduce(vda) | |
} | |
switch typ := (<-retchan).(type) { | |
case pathDeduction: | |
return typ, nil | |
case error: | |
return pathDeduction{}, typ | |
default: | |
panic("unreachable") | |
} | |
} | |
// pathDeduction represents the results of a successful import path deduction - | |
// a root path, plus a maybeSource that can be used to attempt to connect to | |
// the source. | |
type pathDeduction struct { | |
root string | |
mb maybeSource | |
} | |
var noKnownPathMatch = errors.New("no known path match") | |
func (dc *deductionCoordinator) deduceKnownPaths(path string) (pathDeduction, error) { | |
u, path, err := normalizeURI(path) | |
if err != nil { | |
return pathDeduction{}, err | |
} | |
// First, try the root path-based matches | |
if _, mtch, has := dc.deducext.LongestPrefix(path); has { | |
root, err := mtch.deduceRoot(path) | |
if err != nil { | |
return pathDeduction{}, err | |
} | |
mb, err := mtch.deduceSource(path, u) | |
if err != nil { | |
return pathDeduction{}, err | |
} | |
return pathDeduction{ | |
root: root, | |
mb: mb, | |
}, nil | |
} | |
// Next, try the vcs extension-based (infix) matcher | |
exm := vcsExtensionDeducer{regexp: vcsExtensionRegex} | |
if root, err := exm.deduceRoot(path); err == nil { | |
mb, err := exm.deduceSource(path, u) | |
if err != nil { | |
return pathDeduction{}, err | |
} | |
return pathDeduction{ | |
root: root, | |
mb: mb, | |
}, nil | |
} | |
return pathDeduction{}, noKnownPathMatch | |
} | |
type vanityDeducerActor struct { | |
once sync.Once | |
deduced pathDeduction | |
deduceErr error | |
basePath string | |
returnFunc func(pathDeduction) | |
callMgr callManager | |
ctx context.Context | |
} | |
func (vda *vanityDeducerActor) deduce(path string) (pathDeduction, error) { | |
vda.once.Do(func() { | |
// FIXME interact with callmgr | |
//vda.callMgr.Attach() | |
opath := path | |
// FIXME should we need this first return val? | |
u, path, err := normalizeURI(path) | |
if err != nil { | |
vda.deduceErr = err | |
return | |
} | |
pd := pathDeduction{} | |
// Make the HTTP call to attempt to retrieve go-get metadata | |
root, vcs, reporoot, err := parseMetadata(path, vda.ctx) | |
if err != nil { | |
vda.deduceErr = fmt.Errorf("unable to deduce repository and source type for: %q", opath) | |
return | |
} | |
pd.root = root | |
// If we got something back at all, then it supercedes the actual input for | |
// the real URL to hit | |
repoURL, err := url.Parse(reporoot) | |
if err != nil { | |
vda.deduceErr = fmt.Errorf("server returned bad URL when searching for vanity import: %q", reporoot) | |
return | |
} | |
switch vcs { | |
case "git": | |
pd.mb = maybeGitSource{url: repoURL} | |
case "bzr": | |
pd.mb = maybeBzrSource{url: repoURL} | |
case "hg": | |
pd.mb = maybeHgSource{url: repoURL} | |
default: | |
vda.deduceErr = fmt.Errorf("unsupported vcs type %s in go-get metadata from %s", vcs, path) | |
return | |
} | |
vda.deduced = pd | |
// All data is assigned for other goroutines that may be waiting. Now, | |
// send the pathDeduction back to the deductionCoordinator by calling | |
// the returnFunc. This will also remove the reference to this vda in | |
// the coordinator's trie. | |
// | |
// When this call finishes, it is guaranteed the coordinator will have | |
// at least begun running the action to insert the path deduction, which | |
// means no other deduction request will be able to interleave and | |
// request the same path before the pathDeduction can be processed, but | |
// after this vda has been dereferenced from the trie. | |
vda.returnFunc(pd) | |
}) | |
return vda.deduced, vda.deduceErr | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment