2017-05-02 19:26:49 +03:00
|
|
|
// Copyright 2017 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.
|
|
|
|
|
2016-07-12 21:56:12 +03:00
|
|
|
package gps
|
2016-03-16 23:34:09 +03:00
|
|
|
|
2016-03-31 08:10:24 +03:00
|
|
|
import (
|
2017-03-20 08:50:01 +03:00
|
|
|
"context"
|
2016-03-31 08:10:24 +03:00
|
|
|
"fmt"
|
2016-04-05 08:47:22 +03:00
|
|
|
"os"
|
2016-09-10 09:24:20 +03:00
|
|
|
"os/signal"
|
2016-07-29 23:55:23 +03:00
|
|
|
"path/filepath"
|
2016-09-12 18:17:43 +03:00
|
|
|
"runtime"
|
2016-07-29 23:55:23 +03:00
|
|
|
"strings"
|
2016-08-01 23:21:02 +03:00
|
|
|
"sync"
|
2016-09-10 09:24:20 +03:00
|
|
|
"sync/atomic"
|
2016-12-30 18:49:17 +03:00
|
|
|
"time"
|
2017-03-11 01:43:16 +03:00
|
|
|
|
2017-05-10 07:13:22 +03:00
|
|
|
"github.com/golang/dep/internal/gps/pkgtree"
|
2017-04-25 20:13:14 +03:00
|
|
|
"github.com/sdboyer/constext"
|
2016-03-31 08:10:24 +03:00
|
|
|
)
|
|
|
|
|
2017-03-20 08:50:01 +03:00
|
|
|
// Used to compute a friendly filepath from a URL-shaped input.
|
|
|
|
var sanitizer = strings.NewReplacer("-", "--", ":", "-", "/", "-", "+", "-")
|
2016-07-29 23:55:23 +03:00
|
|
|
|
2016-06-30 04:53:15 +03:00
|
|
|
// A SourceManager is responsible for retrieving, managing, and interrogating
|
|
|
|
// source repositories. Its primary purpose is to serve the needs of a Solver,
|
|
|
|
// but it is handy for other purposes, as well.
|
|
|
|
//
|
2016-07-28 04:46:52 +03:00
|
|
|
// gps's built-in SourceManager, SourceMgr, is intended to be generic and
|
|
|
|
// sufficient for any purpose. It provides some additional semantics around the
|
|
|
|
// methods defined here.
|
2016-03-16 23:34:09 +03:00
|
|
|
type SourceManager interface {
|
2016-08-10 08:45:17 +03:00
|
|
|
// SourceExists checks if a repository exists, either upstream or in the
|
2016-06-30 04:53:15 +03:00
|
|
|
// SourceManager's central repository cache.
|
2016-08-10 08:45:17 +03:00
|
|
|
SourceExists(ProjectIdentifier) (bool, error)
|
2016-06-30 04:53:15 +03:00
|
|
|
|
2016-08-18 05:41:59 +03:00
|
|
|
// SyncSourceFor will attempt to bring all local information about a source
|
|
|
|
// fully up to date.
|
|
|
|
SyncSourceFor(ProjectIdentifier) error
|
|
|
|
|
2016-06-30 04:53:15 +03:00
|
|
|
// ListVersions retrieves a list of the available versions for a given
|
|
|
|
// repository name.
|
2017-03-29 19:54:05 +03:00
|
|
|
// TODO convert to []PairedVersion
|
2017-04-06 12:26:05 +03:00
|
|
|
ListVersions(ProjectIdentifier) ([]PairedVersion, error)
|
2016-06-30 04:53:15 +03:00
|
|
|
|
2016-07-10 08:52:20 +03:00
|
|
|
// RevisionPresentIn indicates whether the provided Version is present in
|
|
|
|
// the given repository.
|
2016-07-28 04:46:52 +03:00
|
|
|
RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)
|
2016-07-06 04:44:09 +03:00
|
|
|
|
2016-07-28 04:46:52 +03:00
|
|
|
// ListPackages parses the tree of the Go packages at or below root of the
|
|
|
|
// provided ProjectIdentifier, at the provided version.
|
2017-03-11 01:43:16 +03:00
|
|
|
ListPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
|
2016-06-30 04:53:15 +03:00
|
|
|
|
2016-07-22 18:28:27 +03:00
|
|
|
// GetManifestAndLock returns manifest and lock information for the provided
|
|
|
|
// root import path.
|
|
|
|
//
|
2016-07-28 04:46:52 +03:00
|
|
|
// gps currently requires that projects be rooted at their repository root,
|
|
|
|
// necessitating that the ProjectIdentifier's ProjectRoot must also be a
|
2016-06-30 04:53:15 +03:00
|
|
|
// repository root.
|
2017-04-01 08:14:58 +03:00
|
|
|
GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
|
2016-07-28 04:46:52 +03:00
|
|
|
|
|
|
|
// ExportProject writes out the tree of the provided import path, at the
|
|
|
|
// provided version, to the provided directory.
|
|
|
|
ExportProject(ProjectIdentifier, Version, string) error
|
2016-06-30 04:53:15 +03:00
|
|
|
|
2016-08-15 17:58:20 +03:00
|
|
|
// DeduceRootProject takes an import path and deduces the corresponding
|
|
|
|
// project/source root.
|
|
|
|
DeduceProjectRoot(ip string) (ProjectRoot, error)
|
2017-04-10 22:01:27 +03:00
|
|
|
|
|
|
|
// Release lets go of any locks held by the SourceManager. Once called, it is
|
|
|
|
// no longer safe to call methods against it; all method calls will
|
|
|
|
// immediately result in errors.
|
|
|
|
Release()
|
2016-03-30 20:51:23 +03:00
|
|
|
}
|
|
|
|
|
2016-07-22 08:54:40 +03:00
|
|
|
// A ProjectAnalyzer is responsible for analyzing a given path for Manifest and
|
|
|
|
// Lock information. Tools relying on gps must implement one.
|
2016-06-30 05:47:25 +03:00
|
|
|
type ProjectAnalyzer interface {
|
2016-07-22 18:28:27 +03:00
|
|
|
// Perform analysis of the filesystem tree rooted at path, with the
|
|
|
|
// root import path importRoot, to determine the project's constraints, as
|
|
|
|
// indicated by a Manifest and Lock.
|
2017-05-17 06:37:47 +03:00
|
|
|
//
|
|
|
|
// Note that an error will typically cause the solver to treat the analyzed
|
|
|
|
// version as unusable. As such, an error should generally only be returned
|
|
|
|
// if the code tree is somehow malformed, but not if the implementor's
|
|
|
|
// expected files containing Manifest and Lock data are merely absent.
|
2016-07-22 18:28:27 +03:00
|
|
|
DeriveManifestAndLock(path string, importRoot ProjectRoot) (Manifest, Lock, error)
|
2016-08-15 17:58:20 +03:00
|
|
|
|
2017-06-14 19:56:28 +03:00
|
|
|
// Info reports this project analyzer's info.
|
|
|
|
Info() ProjectAnalyzerInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
// ProjectAnalyzerInfo indicates a ProjectAnalyzer's name and version.
|
|
|
|
type ProjectAnalyzerInfo struct {
|
|
|
|
Name string
|
|
|
|
Version int
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string like: "<name>.<decimal version>"
|
|
|
|
func (p ProjectAnalyzerInfo) String() string {
|
|
|
|
return fmt.Sprintf("%s.%d", p.Name, p.Version)
|
2016-06-30 05:47:25 +03:00
|
|
|
}
|
|
|
|
|
2016-07-12 21:56:12 +03:00
|
|
|
// SourceMgr is the default SourceManager for gps.
|
2016-03-31 08:10:24 +03:00
|
|
|
//
|
|
|
|
// There's no (planned) reason why it would need to be reimplemented by other
|
|
|
|
// tools; control via dependency injection is intended to be sufficient.
|
2016-07-10 08:52:20 +03:00
|
|
|
type SourceMgr struct {
|
2017-03-28 14:18:00 +03:00
|
|
|
cachedir string // path to root of cache dir
|
|
|
|
lf *os.File // handle for the sm lock file on disk
|
2017-03-31 13:01:17 +03:00
|
|
|
suprvsr *supervisor // subsystem that supervises running calls/io
|
2017-03-31 19:56:30 +03:00
|
|
|
cancelAll context.CancelFunc // cancel func to kill all running work
|
2017-03-28 14:18:00 +03:00
|
|
|
deduceCoord *deductionCoordinator // subsystem that manages import path deduction
|
|
|
|
srcCoord *sourceCoordinator // subsystem that manages sources
|
|
|
|
sigmut sync.Mutex // mutex protecting signal handling setup/teardown
|
2017-03-31 19:56:30 +03:00
|
|
|
qch chan struct{} // quit chan for signal handler
|
2017-03-28 14:18:00 +03:00
|
|
|
relonce sync.Once // once-er to ensure we only release once
|
|
|
|
releasing int32 // flag indicating release of sm has begun
|
2016-09-10 06:40:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type smIsReleased struct{}
|
|
|
|
|
|
|
|
func (smIsReleased) Error() string {
|
|
|
|
return "this SourceMgr has been released, its methods can no longer be called"
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-07-10 08:52:20 +03:00
|
|
|
var _ SourceManager = &SourceMgr{}
|
|
|
|
|
2016-07-12 21:56:12 +03:00
|
|
|
// NewSourceManager produces an instance of gps's built-in SourceManager. It
|
2017-04-01 08:14:58 +03:00
|
|
|
// takes a cache directory, where local instances of upstream sources are
|
|
|
|
// stored.
|
2016-06-30 04:53:15 +03:00
|
|
|
//
|
2016-07-10 08:52:20 +03:00
|
|
|
// The returned SourceManager aggressively caches information wherever possible.
|
2016-09-22 05:28:09 +03:00
|
|
|
// If tools need to do preliminary work involving upstream repository analysis
|
|
|
|
// prior to invoking a solve run, it is recommended that they create this
|
|
|
|
// SourceManager as early as possible and use it to their ends. That way, the
|
|
|
|
// solver can benefit from any caches that may have already been warmed.
|
2016-06-30 04:53:15 +03:00
|
|
|
//
|
2016-09-22 05:28:09 +03:00
|
|
|
// gps's SourceManager is intended to be threadsafe (if it's not, please file a
|
|
|
|
// bug!). It should be safe to reuse across concurrent solving runs, even on
|
|
|
|
// unrelated projects.
|
2017-04-01 08:14:58 +03:00
|
|
|
func NewSourceManager(cachedir string) (*SourceMgr, error) {
|
2016-07-29 23:55:23 +03:00
|
|
|
err := os.MkdirAll(filepath.Join(cachedir, "sources"), 0777)
|
2016-04-05 22:19:14 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-01 07:28:13 +03:00
|
|
|
glpath := filepath.Join(cachedir, "sm.lock")
|
2016-04-05 22:19:14 +03:00
|
|
|
_, err = os.Stat(glpath)
|
2016-09-22 05:28:09 +03:00
|
|
|
if err == nil {
|
|
|
|
return nil, CouldNotCreateLockError{
|
|
|
|
Path: glpath,
|
|
|
|
Err: fmt.Errorf("cache lock file %s exists - another process crashed or is still running?", glpath),
|
|
|
|
}
|
2016-04-05 22:19:14 +03:00
|
|
|
}
|
2016-04-06 07:08:28 +03:00
|
|
|
|
2016-09-22 05:28:09 +03:00
|
|
|
fi, err := os.OpenFile(glpath, os.O_CREATE|os.O_EXCL, 0600) // is 0600 sane for this purpose?
|
2016-04-05 22:19:14 +03:00
|
|
|
if err != nil {
|
2016-09-22 05:28:09 +03:00
|
|
|
return nil, CouldNotCreateLockError{
|
|
|
|
Path: glpath,
|
|
|
|
Err: fmt.Errorf("err on attempting to create global cache lock: %s", err),
|
|
|
|
}
|
2016-04-05 22:19:14 +03:00
|
|
|
}
|
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
ctx, cf := context.WithCancel(context.TODO())
|
|
|
|
superv := newSupervisor(ctx)
|
2017-03-31 13:01:17 +03:00
|
|
|
deducer := newDeductionCoordinator(superv)
|
2017-03-20 08:50:01 +03:00
|
|
|
|
2016-09-10 09:24:20 +03:00
|
|
|
sm := &SourceMgr{
|
2017-03-20 08:50:01 +03:00
|
|
|
cachedir: cachedir,
|
|
|
|
lf: fi,
|
2017-03-31 13:01:17 +03:00
|
|
|
suprvsr: superv,
|
2017-03-31 19:56:30 +03:00
|
|
|
cancelAll: cf,
|
2017-03-20 08:50:01 +03:00
|
|
|
deduceCoord: deducer,
|
2017-03-31 13:01:17 +03:00
|
|
|
srcCoord: newSourceCoordinator(superv, deducer, cachedir),
|
2017-03-20 08:50:01 +03:00
|
|
|
qch: make(chan struct{}),
|
2016-09-10 09:24:20 +03:00
|
|
|
}
|
|
|
|
|
2016-12-30 19:35:06 +03:00
|
|
|
return sm, nil
|
|
|
|
}
|
|
|
|
|
2017-01-11 06:00:30 +03:00
|
|
|
// UseDefaultSignalHandling sets up typical os.Interrupt signal handling for a
|
2017-01-01 08:07:21 +03:00
|
|
|
// SourceMgr.
|
2017-01-11 06:00:30 +03:00
|
|
|
func (sm *SourceMgr) UseDefaultSignalHandling() {
|
2017-01-01 08:07:21 +03:00
|
|
|
sigch := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigch, os.Interrupt)
|
2016-12-30 19:35:06 +03:00
|
|
|
sm.HandleSignals(sigch)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleSignals sets up logic to handle incoming signals with the goal of
|
|
|
|
// shutting down the SourceMgr safely.
|
|
|
|
//
|
|
|
|
// Calling code must provide the signal channel, and is responsible for calling
|
|
|
|
// signal.Notify() on that channel.
|
|
|
|
//
|
|
|
|
// Successive calls to HandleSignals() will deregister the previous handler and
|
|
|
|
// set up a new one. It is not recommended that the same channel be passed
|
|
|
|
// multiple times to this method.
|
|
|
|
//
|
|
|
|
// SetUpSigHandling() will set up a handler that is appropriate for most
|
|
|
|
// use cases.
|
|
|
|
func (sm *SourceMgr) HandleSignals(sigch chan os.Signal) {
|
|
|
|
sm.sigmut.Lock()
|
|
|
|
// always start by closing the qch, which will lead to any existing signal
|
|
|
|
// handler terminating, and deregistering its sigch.
|
|
|
|
if sm.qch != nil {
|
|
|
|
close(sm.qch)
|
|
|
|
}
|
|
|
|
sm.qch = make(chan struct{})
|
|
|
|
|
|
|
|
// Run a new goroutine with the input sigch and the fresh qch
|
|
|
|
go func(sch chan os.Signal, qch <-chan struct{}) {
|
2016-12-30 20:23:12 +03:00
|
|
|
defer signal.Stop(sch)
|
2016-09-10 09:24:20 +03:00
|
|
|
for {
|
|
|
|
select {
|
2016-12-30 19:35:06 +03:00
|
|
|
case <-sch:
|
2016-12-30 18:49:17 +03:00
|
|
|
// Set up a timer to uninstall the signal handler after three
|
|
|
|
// seconds, so that the user can easily force termination with a
|
|
|
|
// second ctrl-c
|
|
|
|
go func(c <-chan time.Time) {
|
|
|
|
<-c
|
2016-12-30 19:35:06 +03:00
|
|
|
signal.Stop(sch)
|
2016-12-30 18:49:17 +03:00
|
|
|
}(time.After(3 * time.Second))
|
|
|
|
|
|
|
|
if !atomic.CompareAndSwapInt32(&sm.releasing, 0, 1) {
|
|
|
|
// Something's already called Release() on this sm, so we
|
|
|
|
// don't have to do anything, as we'd just be redoing
|
2016-12-30 20:23:12 +03:00
|
|
|
// that work. Instead, deregister and return.
|
2016-12-30 18:49:17 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
opc := sm.suprvsr.count()
|
2016-12-30 18:49:17 +03:00
|
|
|
if opc > 0 {
|
2017-01-11 06:33:40 +03:00
|
|
|
fmt.Printf("Signal received: waiting for %v ops to complete...\n", opc)
|
2016-09-10 09:24:20 +03:00
|
|
|
}
|
|
|
|
|
2016-12-30 18:49:17 +03:00
|
|
|
// Mutex interaction in a signal handler is, as a general rule,
|
|
|
|
// unsafe. I'm not clear on whether the guarantees Go provides
|
|
|
|
// around signal handling, or having passed this through a
|
|
|
|
// channel in general, obviate those concerns, but it's a lot
|
2017-01-11 06:33:40 +03:00
|
|
|
// easier to just rely on the mutex contained in the Once right
|
|
|
|
// now, so do that until it proves problematic or someone
|
|
|
|
// provides a clear explanation.
|
|
|
|
sm.relonce.Do(func() { sm.doRelease() })
|
2016-09-10 09:24:20 +03:00
|
|
|
return
|
2016-12-30 19:35:06 +03:00
|
|
|
case <-qch:
|
2016-12-30 18:49:17 +03:00
|
|
|
// quit channel triggered - deregister our sigch and return
|
2016-09-10 09:24:20 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2016-12-30 19:35:06 +03:00
|
|
|
}(sigch, sm.qch)
|
|
|
|
// Try to ensure handler is blocked in for-select before releasing the mutex
|
2016-09-12 18:17:43 +03:00
|
|
|
runtime.Gosched()
|
2016-09-10 09:24:20 +03:00
|
|
|
|
2016-12-30 19:35:06 +03:00
|
|
|
sm.sigmut.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopSignalHandling deregisters any signal handler running on this SourceMgr.
|
|
|
|
//
|
|
|
|
// It's normally not necessary to call this directly; it will be called as
|
|
|
|
// needed by Release().
|
|
|
|
func (sm *SourceMgr) StopSignalHandling() {
|
|
|
|
sm.sigmut.Lock()
|
|
|
|
if sm.qch != nil {
|
|
|
|
close(sm.qch)
|
2016-12-30 20:21:17 +03:00
|
|
|
sm.qch = nil
|
2016-12-30 19:35:06 +03:00
|
|
|
runtime.Gosched()
|
|
|
|
}
|
|
|
|
sm.sigmut.Unlock()
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-10-22 04:35:08 +03:00
|
|
|
// CouldNotCreateLockError describe failure modes in which creating a SourceMgr
|
|
|
|
// did not succeed because there was an error while attempting to create the
|
|
|
|
// on-disk lock file.
|
2016-09-22 05:28:09 +03:00
|
|
|
type CouldNotCreateLockError struct {
|
|
|
|
Path string
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e CouldNotCreateLockError) Error() string {
|
|
|
|
return e.Err.Error()
|
|
|
|
}
|
|
|
|
|
2016-09-12 18:17:43 +03:00
|
|
|
// Release lets go of any locks held by the SourceManager. Once called, it is no
|
|
|
|
// longer safe to call methods against it; all method calls will immediately
|
|
|
|
// result in errors.
|
2016-07-10 08:52:20 +03:00
|
|
|
func (sm *SourceMgr) Release() {
|
2017-01-11 06:33:40 +03:00
|
|
|
// Set sm.releasing before entering the Once func to guarantee that no
|
|
|
|
// _more_ method calls will stack up if/while waiting.
|
|
|
|
atomic.CompareAndSwapInt32(&sm.releasing, 0, 1)
|
|
|
|
|
|
|
|
// Whether 'releasing' is set or not, we don't want this function to return
|
|
|
|
// until after the doRelease process is done, as doing so could cause the
|
|
|
|
// process to terminate before a signal-driven doRelease() call has a chance
|
|
|
|
// to finish its cleanup.
|
|
|
|
sm.relonce.Do(func() { sm.doRelease() })
|
|
|
|
}
|
2016-09-10 06:40:52 +03:00
|
|
|
|
2017-01-11 06:33:40 +03:00
|
|
|
// doRelease actually releases physical resources (files on disk, etc.).
|
|
|
|
//
|
|
|
|
// This must be called only and exactly once. Calls to it should be wrapped in
|
|
|
|
// the sm.relonce sync.Once instance.
|
|
|
|
func (sm *SourceMgr) doRelease() {
|
2017-03-31 19:56:30 +03:00
|
|
|
// Send the signal to the supervisor to cancel all running calls
|
|
|
|
sm.cancelAll()
|
|
|
|
sm.suprvsr.wait()
|
2016-04-05 22:19:14 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
// Close the file handle for the lock file and remove it from disk
|
2017-01-11 06:33:40 +03:00
|
|
|
sm.lf.Close()
|
|
|
|
os.Remove(filepath.Join(sm.cachedir, "sm.lock"))
|
2017-03-31 19:56:30 +03:00
|
|
|
|
2017-01-11 06:33:40 +03:00
|
|
|
// Close the qch, if non-nil, so the signal handlers run out. This will
|
|
|
|
// also deregister the sig channel, if any has been set up.
|
|
|
|
if sm.qch != nil {
|
|
|
|
close(sm.qch)
|
2016-09-10 09:24:20 +03:00
|
|
|
}
|
2016-07-22 08:54:40 +03:00
|
|
|
}
|
|
|
|
|
2016-07-28 04:46:52 +03:00
|
|
|
// GetManifestAndLock returns manifest and lock information for the provided
|
2017-04-01 08:14:58 +03:00
|
|
|
// ProjectIdentifier, at the provided Version. The work of producing the
|
|
|
|
// manifest and lock is delegated to the provided ProjectAnalyzer's
|
|
|
|
// DeriveManifestAndLock() method.
|
|
|
|
func (sm *SourceMgr) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return nil, nil, smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-03-31 08:10:24 +03:00
|
|
|
if err != nil {
|
2016-06-30 05:11:59 +03:00
|
|
|
return nil, nil, err
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2017-04-01 08:14:58 +03:00
|
|
|
return srcg.getManifestAndLock(context.TODO(), id.ProjectRoot, v, an)
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-07-28 04:46:52 +03:00
|
|
|
// ListPackages parses the tree of the Go packages at and below the ProjectRoot
|
|
|
|
// of the given ProjectIdentifier, at the given version.
|
2017-03-11 01:43:16 +03:00
|
|
|
func (sm *SourceMgr) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2017-03-11 01:43:16 +03:00
|
|
|
return pkgtree.PackageTree{}, smIsReleased{}
|
2016-09-10 06:40:52 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-06-15 06:57:48 +03:00
|
|
|
if err != nil {
|
2017-03-11 01:43:16 +03:00
|
|
|
return pkgtree.PackageTree{}, err
|
2016-06-15 06:57:48 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
return srcg.listPackages(context.TODO(), id.ProjectRoot, v)
|
2016-06-15 06:57:48 +03:00
|
|
|
}
|
|
|
|
|
2016-06-30 04:53:15 +03:00
|
|
|
// ListVersions retrieves a list of the available versions for a given
|
|
|
|
// repository name.
|
|
|
|
//
|
2016-07-06 04:44:09 +03:00
|
|
|
// The list is not sorted; while it may be returned in the order that the
|
2016-06-30 04:53:15 +03:00
|
|
|
// underlying VCS reports version information, no guarantee is made. It is
|
|
|
|
// expected that the caller either not care about order, or sort the result
|
|
|
|
// themselves.
|
|
|
|
//
|
2016-07-28 04:46:52 +03:00
|
|
|
// This list is always retrieved from upstream on the first call. Subsequent
|
|
|
|
// calls will return a cached version of the first call's results. if upstream
|
|
|
|
// is not accessible (network outage, access issues, or the resource actually
|
|
|
|
// went away), an error will be returned.
|
2017-04-06 12:26:05 +03:00
|
|
|
func (sm *SourceMgr) ListVersions(id ProjectIdentifier) ([]PairedVersion, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return nil, smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-03-31 08:10:24 +03:00
|
|
|
if err != nil {
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) More-er proper-er errors
|
2016-03-31 08:10:24 +03:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-06 12:26:05 +03:00
|
|
|
return srcg.listVersions(context.TODO())
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-07-06 04:44:09 +03:00
|
|
|
// RevisionPresentIn indicates whether the provided Revision is present in the given
|
2016-07-10 08:52:20 +03:00
|
|
|
// repository.
|
2016-07-28 04:46:52 +03:00
|
|
|
func (sm *SourceMgr) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return false, smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-07-06 04:44:09 +03:00
|
|
|
if err != nil {
|
2016-07-12 07:44:02 +03:00
|
|
|
// TODO(sdboyer) More-er proper-er errors
|
2016-07-06 04:44:09 +03:00
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
return srcg.revisionPresentIn(context.TODO(), r)
|
2016-07-06 04:44:09 +03:00
|
|
|
}
|
|
|
|
|
2016-08-10 08:45:17 +03:00
|
|
|
// SourceExists checks if a repository exists, either upstream or in the cache,
|
2016-07-28 04:46:52 +03:00
|
|
|
// for the provided ProjectIdentifier.
|
2016-08-10 08:45:17 +03:00
|
|
|
func (sm *SourceMgr) SourceExists(id ProjectIdentifier) (bool, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return false, smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-04-05 22:19:14 +03:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2017-04-01 20:15:52 +03:00
|
|
|
ctx := context.TODO()
|
|
|
|
return srcg.existsInCache(ctx) || srcg.existsUpstream(ctx), nil
|
2016-03-31 08:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-08-18 05:41:59 +03:00
|
|
|
// SyncSourceFor will ensure that all local caches and information about a
|
|
|
|
// source are up to date with any network-acccesible information.
|
|
|
|
//
|
|
|
|
// The primary use case for this is prefetching.
|
|
|
|
func (sm *SourceMgr) SyncSourceFor(id ProjectIdentifier) error {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-08-18 05:41:59 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
return srcg.syncLocal(context.TODO())
|
2016-08-18 05:41:59 +03:00
|
|
|
}
|
|
|
|
|
2016-07-28 04:46:52 +03:00
|
|
|
// ExportProject writes out the tree of the provided ProjectIdentifier's
|
|
|
|
// ProjectRoot, at the provided version, to the provided directory.
|
|
|
|
func (sm *SourceMgr) ExportProject(id ProjectIdentifier, v Version, to string) error {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return smIsReleased{}
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
srcg, err := sm.srcCoord.getSourceGatewayFor(context.TODO(), id)
|
2016-04-13 17:59:10 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-03-28 05:14:12 +03:00
|
|
|
return srcg.exportVersionTo(context.TODO(), v, to)
|
2016-04-13 17:59:10 +03:00
|
|
|
}
|
|
|
|
|
2016-10-22 04:35:08 +03:00
|
|
|
// DeduceProjectRoot takes an import path and deduces the corresponding
|
2016-08-15 08:48:11 +03:00
|
|
|
// project/source root.
|
2016-08-14 06:21:03 +03:00
|
|
|
//
|
|
|
|
// Note that some import paths may require network activity to correctly
|
|
|
|
// determine the root of the path, such as, but not limited to, vanity import
|
|
|
|
// paths. (A special exception is written for gopkg.in to minimize network
|
|
|
|
// activity, as its behavior is well-structured)
|
|
|
|
func (sm *SourceMgr) DeduceProjectRoot(ip string) (ProjectRoot, error) {
|
2016-09-10 09:24:20 +03:00
|
|
|
if atomic.CompareAndSwapInt32(&sm.releasing, 1, 1) {
|
2016-09-10 06:40:52 +03:00
|
|
|
return "", smIsReleased{}
|
|
|
|
}
|
2016-08-14 06:21:03 +03:00
|
|
|
|
2017-03-28 17:18:28 +03:00
|
|
|
pd, err := sm.deduceCoord.deduceRootPath(context.TODO(), ip)
|
2017-03-28 05:14:12 +03:00
|
|
|
return ProjectRoot(pd.root), err
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:36:39 +03:00
|
|
|
type timeCount struct {
|
|
|
|
count int
|
|
|
|
start time.Time
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:36:39 +03:00
|
|
|
type durCount struct {
|
|
|
|
count int
|
|
|
|
dur time.Duration
|
|
|
|
}
|
2016-08-14 06:21:03 +03:00
|
|
|
|
2017-03-31 13:01:17 +03:00
|
|
|
type supervisor struct {
|
2017-03-28 14:36:39 +03:00
|
|
|
ctx context.Context
|
|
|
|
cancelFunc context.CancelFunc
|
2017-04-01 07:43:06 +03:00
|
|
|
mu sync.Mutex // Guards all maps
|
|
|
|
cond sync.Cond // Wraps mu so callers can wait until all calls end
|
2017-03-28 14:36:39 +03:00
|
|
|
running map[callInfo]timeCount
|
2017-03-31 13:01:17 +03:00
|
|
|
ran map[callType]durCount
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
2016-08-14 06:21:03 +03:00
|
|
|
|
2017-03-31 13:01:17 +03:00
|
|
|
func newSupervisor(ctx context.Context) *supervisor {
|
2017-03-28 14:36:39 +03:00
|
|
|
ctx, cf := context.WithCancel(ctx)
|
2017-04-01 07:43:06 +03:00
|
|
|
supv := &supervisor{
|
2017-03-28 14:36:39 +03:00
|
|
|
ctx: ctx,
|
|
|
|
cancelFunc: cf,
|
|
|
|
running: make(map[callInfo]timeCount),
|
|
|
|
ran: make(map[callType]durCount),
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
|
|
|
|
2017-04-01 07:43:06 +03:00
|
|
|
supv.cond = sync.Cond{L: &supv.mu}
|
|
|
|
return supv
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
|
|
|
|
2017-03-31 07:00:00 +03:00
|
|
|
// do executes the incoming closure using a conjoined context, and keeps
|
|
|
|
// counters to ensure the sourceMgr can't finish Release()ing until after all
|
|
|
|
// calls have returned.
|
2017-03-31 19:56:30 +03:00
|
|
|
func (sup *supervisor) do(inctx context.Context, name string, typ callType, f func(context.Context) error) error {
|
2017-03-28 14:36:39 +03:00
|
|
|
ci := callInfo{
|
|
|
|
name: name,
|
|
|
|
typ: typ,
|
2016-10-17 06:19:34 +03:00
|
|
|
}
|
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
octx, err := sup.start(ci)
|
2016-08-14 06:21:03 +03:00
|
|
|
if err != nil {
|
2017-03-31 07:00:00 +03:00
|
|
|
return err
|
2016-10-17 06:19:34 +03:00
|
|
|
}
|
|
|
|
|
2017-03-28 14:36:39 +03:00
|
|
|
cctx, cancelFunc := constext.Cons(inctx, octx)
|
2017-03-31 07:00:00 +03:00
|
|
|
err = f(cctx)
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.done(ci)
|
2017-03-31 07:00:00 +03:00
|
|
|
cancelFunc()
|
|
|
|
return err
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
func (sup *supervisor) getLifetimeContext() context.Context {
|
|
|
|
return sup.ctx
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
func (sup *supervisor) start(ci callInfo) (context.Context, error) {
|
|
|
|
sup.mu.Lock()
|
|
|
|
defer sup.mu.Unlock()
|
|
|
|
if sup.ctx.Err() != nil {
|
2017-03-28 14:36:39 +03:00
|
|
|
// We've already been canceled; error out.
|
2017-03-31 19:56:30 +03:00
|
|
|
return nil, sup.ctx.Err()
|
2016-10-17 06:19:34 +03:00
|
|
|
}
|
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
if existingInfo, has := sup.running[ci]; has {
|
2017-03-28 14:36:39 +03:00
|
|
|
existingInfo.count++
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.running[ci] = existingInfo
|
2016-10-17 06:19:34 +03:00
|
|
|
} else {
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.running[ci] = timeCount{
|
2017-03-28 14:36:39 +03:00
|
|
|
count: 1,
|
|
|
|
start: time.Now(),
|
|
|
|
}
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
return sup.ctx, nil
|
|
|
|
}
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
func (sup *supervisor) count() int {
|
|
|
|
sup.mu.Lock()
|
|
|
|
defer sup.mu.Unlock()
|
|
|
|
return len(sup.running)
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
func (sup *supervisor) done(ci callInfo) {
|
|
|
|
sup.mu.Lock()
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
existingInfo, has := sup.running[ci]
|
2017-03-28 14:36:39 +03:00
|
|
|
if !has {
|
|
|
|
panic(fmt.Sprintf("sourceMgr: tried to complete a call that had not registered via run()"))
|
|
|
|
}
|
2016-10-17 06:19:34 +03:00
|
|
|
|
2017-03-28 14:36:39 +03:00
|
|
|
if existingInfo.count > 1 {
|
|
|
|
// If more than one is pending, don't stop the clock yet.
|
|
|
|
existingInfo.count--
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.running[ci] = existingInfo
|
2017-03-28 14:36:39 +03:00
|
|
|
} else {
|
|
|
|
// Last one for this particular key; update metrics with info.
|
2017-03-31 19:56:30 +03:00
|
|
|
durCnt := sup.ran[ci.typ]
|
2017-03-28 14:36:39 +03:00
|
|
|
durCnt.count++
|
2017-04-25 20:13:14 +03:00
|
|
|
durCnt.dur += time.Since(existingInfo.start)
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.ran[ci.typ] = durCnt
|
|
|
|
delete(sup.running, ci)
|
|
|
|
|
|
|
|
if len(sup.running) == 0 {
|
|
|
|
// This is the only place where we signal the cond, as it's the only
|
|
|
|
// time that the number of running calls could become zero.
|
|
|
|
sup.cond.Signal()
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.mu.Unlock()
|
|
|
|
}
|
2016-08-14 06:21:03 +03:00
|
|
|
|
2017-03-31 19:56:30 +03:00
|
|
|
// wait until all active calls have terminated.
|
|
|
|
//
|
|
|
|
// Assumes something else has already canceled the supervisor via its context.
|
|
|
|
func (sup *supervisor) wait() {
|
|
|
|
sup.cond.L.Lock()
|
|
|
|
for len(sup.running) > 0 {
|
|
|
|
sup.cond.Wait()
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|
2017-03-31 19:56:30 +03:00
|
|
|
sup.cond.L.Unlock()
|
2017-03-28 14:36:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type callType uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
ctHTTPMetadata callType = iota
|
|
|
|
ctListVersions
|
|
|
|
ctGetManifestAndLock
|
2017-03-31 07:00:00 +03:00
|
|
|
ctListPackages
|
|
|
|
ctSourcePing
|
|
|
|
ctSourceInit
|
|
|
|
ctSourceFetch
|
|
|
|
ctCheckoutVersion
|
|
|
|
ctExportTree
|
2017-03-28 14:36:39 +03:00
|
|
|
)
|
2016-08-14 06:21:03 +03:00
|
|
|
|
2017-03-28 14:36:39 +03:00
|
|
|
// callInfo provides metadata about an ongoing call.
|
|
|
|
type callInfo struct {
|
|
|
|
name string
|
|
|
|
typ callType
|
2016-08-14 06:21:03 +03:00
|
|
|
}
|